Initial commit
This commit is contained in:
commit
c909d539bc
7178 files changed
+756624
No files matched your search
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Slot;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class BookingController extends Controller
|
||||
{
|
||||
public function create(Request $request)
|
||||
{
|
||||
// Validate query params: /admin/bookings/create?date=YYYY-MM-DD&slot_id=123
|
||||
$data = $request->validate([
|
||||
'date' => ['required','date_format:Y-m-d'],
|
||||
'slot_id' => ['required','exists:slots,id'],
|
||||
]);
|
||||
|
||||
$slot = Slot::findOrFail($data['slot_id']);
|
||||
|
||||
// Build nice labels (Europe/London) so Blade doesn't need Carbon
|
||||
$when = Carbon::createFromFormat('Y-m-d H:i:s', "{$data['date']} {$slot->time}", 'Europe/London');
|
||||
$prettyDate = $when->format('D d M Y');
|
||||
$prettyTime = $when->format('H:i');
|
||||
$weekdayName = $when->format('l');
|
||||
|
||||
return view('bookings.create', [
|
||||
'slot' => $slot,
|
||||
'date' => $data['date'],
|
||||
'prettyDate' => $prettyDate,
|
||||
'prettyTime' => $prettyTime,
|
||||
'weekdayName' => $weekdayName,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(\Illuminate\Http\Request $request)
|
||||
{
|
||||
// Needed at top of file (ensure these use's exist):
|
||||
// use App\Models\Slot;
|
||||
// use App\Models\Booking;
|
||||
// use Carbon\Carbon;
|
||||
// use Illuminate\Validation\ValidationException;
|
||||
// use Illuminate\Database\QueryException;
|
||||
|
||||
$data = $request->validate([
|
||||
'slot_id' => ['required','exists:slots,id'],
|
||||
'date' => ['required','date_format:Y-m-d'],
|
||||
'student_name' => ['required','string','max:255'],
|
||||
'booked_from_page' => ['nullable','string','max:255'],
|
||||
'message' => ['nullable','string'],
|
||||
]);
|
||||
|
||||
// Enforce > 2 hours' notice (exactly 120 mins = NOT allowed)
|
||||
$slot = \App\Models\Slot::findOrFail($data['slot_id']);
|
||||
$sessionStart = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', "{$data['date']} {$slot->time}", 'Europe/London');
|
||||
$now = \Carbon\Carbon::now('Europe/London');
|
||||
$minutesUntil = $now->diffInMinutes($sessionStart, false);
|
||||
|
||||
if ($minutesUntil <= 120) {
|
||||
throw \Illuminate\Validation\ValidationException::withMessages([
|
||||
'date' => 'Bookings require more than 2 hours’ notice.',
|
||||
]);
|
||||
}
|
||||
|
||||
// Persist booking; default status for admin-created bookings = 'booked'
|
||||
$data['status'] = 'booked';
|
||||
|
||||
try {
|
||||
\App\Models\Booking::create($data);
|
||||
} catch (\Illuminate\Database\QueryException $e) {
|
||||
// Handle unique (slot_id, date) violation gracefully
|
||||
if (isset($e->errorInfo[1]) && (int)$e->errorInfo[1] === 1062) {
|
||||
throw \Illuminate\Validation\ValidationException::withMessages([
|
||||
'slot_id' => 'That slot has just been taken. Please choose another.',
|
||||
]);
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('admin.calendar')
|
||||
->with('success', 'Booking created successfully.');
|
||||
}
|
||||
|
||||
public function createBlock(\Illuminate\Http\Request $request)
|
||||
{
|
||||
// Needed at top of file:
|
||||
// use App\Models\Slot;
|
||||
// use Carbon\Carbon;
|
||||
|
||||
$data = $request->validate([
|
||||
'date' => ['required','date_format:Y-m-d'],
|
||||
'slot_id' => ['required','exists:slots,id'],
|
||||
]);
|
||||
|
||||
$slot = \App\Models\Slot::findOrFail($data['slot_id']);
|
||||
|
||||
// Prettify for the view (no Carbon in Blade)
|
||||
$when = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', "{$data['date']} {$slot->time}", 'Europe/London');
|
||||
$prettyDate = $when->format('D d M Y');
|
||||
$prettyTime = $when->format('H:i');
|
||||
$weekdayName = $when->format('l');
|
||||
|
||||
return view('bookings.block', [
|
||||
'slot' => $slot,
|
||||
'date' => $data['date'],
|
||||
'prettyDate' => $prettyDate,
|
||||
'prettyTime' => $prettyTime,
|
||||
'weekdayName' => $weekdayName,
|
||||
]);
|
||||
}
|
||||
|
||||
public function storeBlock(\Illuminate\Http\Request $request)
|
||||
{
|
||||
// Validation: block a specific slot on a specific date
|
||||
$data = $request->validate([
|
||||
'slot_id' => ['required','exists:slots,id'],
|
||||
'date' => ['required','date_format:Y-m-d'],
|
||||
'message' => ['nullable','string'], // optional reason/notes for the block
|
||||
]);
|
||||
|
||||
// Create a "blocked" booking for that slot/date
|
||||
try {
|
||||
\App\Models\Booking::create([
|
||||
'slot_id' => (int)$data['slot_id'],
|
||||
'date' => $data['date'],
|
||||
'student_name' => null, // blocks don't need a student
|
||||
'booked_from_page' => 'admin:block', // provenance
|
||||
'message' => $data['message'] ?? null,
|
||||
'status' => 'blocked',
|
||||
]);
|
||||
} catch (\Illuminate\Database\QueryException $e) {
|
||||
// Unique (slot_id, date) hit — something already exists for that slot/date
|
||||
if (isset($e->errorInfo[1]) && (int)$e->errorInfo[1] === 1062) {
|
||||
return back()
|
||||
->withErrors(['slot_id' => 'That slot/date already has a booking or block.'])
|
||||
->withInput();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('admin.calendar')
|
||||
->with('success', 'Slot blocked successfully.');
|
||||
}
|
||||
|
||||
public function createSeries(\Illuminate\Http\Request $request)
|
||||
{
|
||||
// Needed at top of file (ensure these exist):
|
||||
// use App\Models\Slot;
|
||||
// use Carbon\Carbon;
|
||||
|
||||
// Accept prefill from calendar click: ?date=YYYY-MM-DD&slot_id=NN&weeks=6
|
||||
$data = $request->validate([
|
||||
'date' => ['required','date_format:Y-m-d'],
|
||||
'slot_id' => ['required','exists:slots,id'],
|
||||
'weeks' => ['nullable','integer','min:1','max:52'],
|
||||
]);
|
||||
|
||||
$slot = \App\Models\Slot::findOrFail($data['slot_id']);
|
||||
|
||||
// Precompute nice labels (keep Blade Carbon-free)
|
||||
$when = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', "{$data['date']} {$slot->time}", 'Europe/London');
|
||||
$prettyDate = $when->format('D d M Y');
|
||||
$prettyTime = $when->format('H:i');
|
||||
$weekdayName = $when->format('l');
|
||||
|
||||
$weeks = $data['weeks'] ?? 6; // sensible default
|
||||
|
||||
return view('bookings.series', [
|
||||
'slot' => $slot,
|
||||
'date' => $data['date'],
|
||||
'prettyDate' => $prettyDate,
|
||||
'prettyTime' => $prettyTime,
|
||||
'weekdayName' => $weekdayName,
|
||||
'weeks' => $weeks,
|
||||
]);
|
||||
}
|
||||
|
||||
public function storeSeries(\Illuminate\Http\Request $request)
|
||||
{
|
||||
// Needed at top of file:
|
||||
// use App\Models\Slot;
|
||||
// use App\Models\Booking;
|
||||
// use Carbon\Carbon;
|
||||
// use Illuminate\Database\QueryException;
|
||||
|
||||
$data = $request->validate([
|
||||
'slot_id' => ['required','exists:slots,id'],
|
||||
'date' => ['required','date_format:Y-m-d'], // start date
|
||||
'weeks' => ['required','integer','min:1','max:52'],
|
||||
]);
|
||||
|
||||
$slot = \App\Models\Slot::findOrFail($data['slot_id']);
|
||||
$now = \Carbon\Carbon::now('Europe/London');
|
||||
$startDate = \Carbon\Carbon::createFromFormat('Y-m-d', $data['date'], 'Europe/London');
|
||||
|
||||
$created = 0;
|
||||
$skipped = 0;
|
||||
|
||||
for ($i = 0; $i < (int)$data['weeks']; $i++) {
|
||||
$date = $startDate->copy()->addWeeks($i)->toDateString();
|
||||
$sessionStart = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', "{$date} {$slot->time}", 'Europe/London');
|
||||
|
||||
// Skip past or ≤ 2 hours from now
|
||||
$minutesUntil = $now->diffInMinutes($sessionStart, false);
|
||||
if ($minutesUntil <= 120) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
\App\Models\Booking::create([
|
||||
'slot_id' => $slot->id,
|
||||
'date' => $date,
|
||||
'student_name' => null, // series = no student name
|
||||
'booked_from_page' => 'admin:block-series', // provenance
|
||||
'message' => null,
|
||||
'status' => 'booked', // shows as “Booked” on the calendar
|
||||
]);
|
||||
$created++;
|
||||
} catch (\Illuminate\Database\QueryException $e) {
|
||||
// Skip duplicates (unique (slot_id, date) might already exist as booked/closed)
|
||||
if (isset($e->errorInfo[1]) && (int)$e->errorInfo[1] === 1062) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect back to the week containing the start date (nice UX)
|
||||
$displayStart = $now->copy()->startOfWeek(\Carbon\Carbon::MONDAY);
|
||||
$fridayCutoff = $displayStart->copy()->addDays(4)->setTime(15, 0);
|
||||
if ($now->greaterThanOrEqualTo($fridayCutoff)) {
|
||||
$displayStart->addWeek();
|
||||
}
|
||||
$weekOffset = $displayStart->diffInWeeks($startDate->copy()->startOfWeek(\Carbon\Carbon::MONDAY), false);
|
||||
|
||||
return redirect()
|
||||
->route('admin.calendar', ['week' => $weekOffset ?: null])
|
||||
->with('success', "Series created: {$created} added, {$skipped} skipped.");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user