Initial Commit
tests / ci (push) Canceled after 0s

This commit is contained in:
Mark Gregory
2026-10-15 17:10:00 +01:00
parent 4b5a9e9aac
commit 8972d80288
3 changed files with 34 additions and 1 deletions
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="csrf-token" content="{{ csrf_token() }}"><title>@yield('title', 'Administration') · Cresta 31</title><link rel="stylesheet" href="{{ asset('css/cresta-admin.css') }}"></head>
<body><div class="admin-shell" id="admin-shell"><aside class="sidebar" id="sidebar"><a class="brand" href="{{ route('lodge.admin') }}"><span class="brand-mark">31</span><span><strong>CRESTA 31</strong><small>MANAGEMENT SUITE</small></span></a><div class="nav-caption">WORKSPACE</div><nav class="side-nav" aria-label="Administration">
<a class="nav-link {{ request()->routeIs('dashboard') || request()->routeIs('lodge.admin') && !request()->has('section') ? 'active' : '' }}" href="{{ route('lodge.admin') }}"><span class="nav-icon">▦</span>Overview</a>
<a class="nav-link {{ request()->routeIs('lodge.admin') && request('section') === 'bookings' ? 'active' : '' }}" href="{{ route('lodge.admin', ['section'=>'bookings']) }}"><span class="nav-icon">▤</span>Bookings</a>
<a class="nav-link {{ request()->routeIs('lodge.admin') && request('section') === 'photos' ? 'active' : '' }}" href="{{ route('lodge.admin', ['section'=>'photos']) }}"><span class="nav-icon">▧</span>Photographs</a>
@if(Route::has('admin.modules.index'))<a class="nav-link {{ request()->routeIs('admin.modules.*') ? 'active' : '' }}" href="{{ route('admin.modules.index') }}"><span class="nav-icon">▥</span>Modules</a>@elseif(Route::has('modules.admin.index'))<a class="nav-link" href="{{ route('modules.admin.index') }}"><span class="nav-icon">▥</span>Modules</a>@endif
</nav><div class="nav-caption">ACCOUNT</div><nav class="side-nav"><a class="nav-link" href="{{ url('/') }}" target="_blank" rel="noopener"><span class="nav-icon">↗</span>View website</a>@if(Route::has('profile.edit'))<a class="nav-link" href="{{ route('profile.edit') }}"><span class="nav-icon">⚙</span>Profile & settings</a>@endif</nav><div class="sidebar-bottom"><span class="status-dot"></span> Site administration <small>CRESTA 31 · {{ date('Y') }}</small></div></aside>
<div class="main-area"><header class="topbar"><div class="topbar-left"><button class="menu-toggle" type="button" aria-label="Toggle navigation" aria-controls="sidebar" aria-expanded="false" onclick="var s=document.getElementById('admin-shell');s.classList.toggle('menu-open');this.setAttribute('aria-expanded',s.classList.contains('menu-open'))">☰</button><div class="crumb">Cresta 31 <span>/</span> <strong>@yield('section-label','Overview')</strong></div></div><div class="topbar-right"><span class="site-pill"><span class="status-dot"></span> Site online</span><div class="avatar">{{ strtoupper(substr(auth()->user()->name ?? auth()->user()->email ?? 'A', 0, 1)) }}</div><div class="account-name">{{ auth()->user()->name ?? auth()->user()->email }}</div><form action="{{ route('logout') }}" method="POST">@csrf<button class="signout" type="submit">Sign out</button></form></div></header><main class="content">@if(session('success'))<div class="notice success">{{ session('success') }}</div>@endif@if($errors->any())<div class="notice error">{{ $errors->first() }}</div>@endif @yield('content')</main><footer class="footer">Cresta 31 <span>Property administration</span></footer></div></div><div class="mobile-shade" onclick="document.getElementById('admin-shell').classList.remove('menu-open')"></div></body></html>
+23 -1
View File
@@ -1 +1,23 @@
@extends('lodge.layout')@section('title','Manage bookings | Cresta 31')@section('content')<div class="subhero"><div class="wrap"><h1>Manage bookings</h1></div></div><main class="section wrap">@if(session('success'))<p>{{ session('success') }}</p>@endif@if($errors->any())<div class="errors">{{ $errors->first() }}</div>@endif<div style="overflow-x:auto"><table><thead><tr><th>Reference</th><th>Guest</th><th>Dates</th><th>Guests</th><th>Total</th><th>Status</th><th>Action</th></tr></thead><tbody>@forelse($bookings as $b)<tr><td>{{ $b->reference }}</td><td>{{ $b->guest_name }}<br><small>{{ $b->guest_email }}<br>{{ $b->guest_phone }}</small></td><td>{{ $b->check_in->format('d M Y') }} – {{ $b->check_out->format('d M Y') }}</td><td>{{ $b->guests }}</td><td>£{{ number_format($b->total_pence/100,2) }}</td><td>{{ ucfirst($b->status) }}</td><td><form method="post" action="{{ route('lodge.update',$b) }}">@csrf @method('PATCH')<select name="status"><option value="pending" @selected($b->status==='pending')>Pending</option><option value="confirmed" @selected($b->status==='confirmed')>Confirmed</option><option value="cancelled" @selected($b->status==='cancelled')>Cancelled</option></select><button style="margin-top:8px">Save</button></form></td></tr>@empty<tr><td colspan="7">No bookings yet.</td></tr>@endforelse</tbody></table></div><div style="margin-top:25px">{{ $bookings->links() }}</div><section style="margin-top:45px"><h2>Lodge photographs</h2><p>Upload JPEG, PNG or WebP photographs. Images are automatically resized and converted to WebP, and remain available after deployments.</p><form action="{{ route('lodge.photos.store') }}" method="post" enctype="multipart/form-data" style="background:#fff;padding:24px">@csrf<label for="photos">Photographs (up to 10 at once, 15 MB each)</label><input id="photos" name="photos[]" type="file" accept="image/jpeg,image/png,image/webp" multiple required><button style="margin-top:15px">Upload and optimise</button></form><div class="gallery" style="margin-top:25px">@foreach($photos as $photo)<div><img src="{{ asset('storage/'.$photo->thumbnail_path) }}" alt="{{ $photo->alt_text }}" loading="lazy"><form action="{{ route('lodge.photos.destroy',$photo) }}" method="post" onsubmit="return confirm('Delete this photograph?')">@csrf @method('DELETE')<button style="margin-top:8px">Delete</button></form></div>@endforeach</div></section></main>@endsection
@extends('lodge.admin-layout')
@section('title','Bookings & overview')
@section('section-label',request('section') === 'photos' ? 'Photographs' : (request('section') === 'bookings' ? 'Bookings' : 'Overview'))
@section('content')
@php
$section = request('section', 'overview');
$items = $bookings->getCollection();
$allCount = $bookings->total();
$pendingOnPage = $items->where('status','pending')->count();
$confirmedOnPage = $items->where('status','confirmed')->count();
$revenueOnPage = $items->where('status','confirmed')->sum('total_pence') / 100;
@endphp
@if($section === 'photos')
<div class="page-heading"><div><div class="eyebrow">PROPERTY MEDIA</div><h1>Photographs</h1><p>Manage the photographs displayed on your lodge website.</p></div><a class="btn secondary" href="{{ route('lodge.admin') }}">← Back to overview</a></div>
@if(Route::has('lodge.photos.store'))<section class="panel"><div class="panel-heading"><div><h2>Upload photographs</h2><p>Add new images to your lodge gallery.</p></div></div><form method="POST" action="{{ route('lodge.photos.store') }}" enctype="multipart/form-data" class="upload-form">@csrf<label for="photo-upload">Select image</label><input id="photo-upload" type="file" name="photo" accept="image/*" required><button class="btn primary" type="submit">Upload photograph</button></form></section>@endif
@if(isset($photos) && count($photos))<section class="panel"><div class="panel-heading"><h2>Gallery</h2><span class="count-chip">{{ count($photos) }} photos</span></div><div class="photo-grid">@foreach($photos as $photo)<div class="photo-card">@php $photoPath = $photo->url ?? $photo->path ?? $photo->filename ?? null; @endphp @if($photoPath)<img loading="lazy" src="{{ str_starts_with($photoPath,'http') || str_starts_with($photoPath,'/') ? $photoPath : asset('storage/'.$photoPath) }}" alt="Lodge photograph">@else<div class="photo-placeholder">Image</div>@endif<div class="photo-caption"><span>Photo #{{ $photo->id }}</span>@if(Route::has('lodge.photos.destroy'))<form method="POST" action="{{ route('lodge.photos.destroy',$photo) }}" onsubmit="return confirm('Delete this photograph?')">@csrf @method('DELETE')<button class="link-danger" type="submit">Delete</button></form>@endif</div></div>@endforeach</div></section>@elseif(!Route::has('lodge.photos.store'))<div class="empty-panel">Photo management is not installed in this version of Cresta 31.</div>@else<div class="empty-panel">No uploaded photographs yet.</div>@endif
@else
<div class="page-heading"><div><div class="eyebrow">PROPERTY MANAGEMENT</div><h1>{{ $section === 'bookings' ? 'Bookings' : 'Welcome back' }}</h1><p>{{ $section === 'bookings' ? 'Review reservations and update booking statuses.' : 'Here’s what’s happening with your lodge bookings.' }}</p></div><a class="btn secondary" href="{{ url('/') }}" target="_blank" rel="noopener">View website ↗</a></div>
<div class="stats-grid"><div class="stat-card"><div class="stat-label">Total bookings <span>▤</span></div><div class="stat-value">{{ number_format($allCount) }}</div><div class="stat-foot">All reservations</div></div><div class="stat-card"><div class="stat-label">Pending <span>◷</span></div><div class="stat-value">{{ $pendingOnPage }}</div><div class="stat-foot">On this page</div></div><div class="stat-card"><div class="stat-label">Confirmed <span>✓</span></div><div class="stat-value">{{ $confirmedOnPage }}</div><div class="stat-foot">On this page</div></div><div class="stat-card"><div class="stat-label">Confirmed value <span>£</span></div><div class="stat-value">£{{ number_format($revenueOnPage,2) }}</div><div class="stat-foot">On this page</div></div></div>
<section class="panel"><div class="panel-heading"><div><h2>{{ $section === 'bookings' ? 'All bookings' : 'Recent bookings' }}</h2><p>Manage guest reservations and their current status.</p></div><span class="count-chip">{{ number_format($allCount) }} total</span></div><div class="table-scroll"><table class="booking-table"><thead><tr><th>Reference</th><th>Guest</th><th>Stay dates</th><th>Guests</th><th>Amount</th><th>Status</th><th>Update</th></tr></thead><tbody>@forelse($bookings as $b)<tr><td><strong class="reference">{{ $b->reference }}</strong></td><td><strong>{{ $b->guest_name }}</strong><small>{{ $b->guest_email }}</small>@if($b->guest_phone)<small>{{ $b->guest_phone }}</small>@endif</td><td><span class="date-cell">{{ $b->check_in?->format('d M Y') }}<br><span class="date-to">to {{ $b->check_out?->format('d M Y') }}</span></span></td><td>{{ $b->guests }}</td><td><strong>£{{ number_format($b->total_pence/100,2) }}</strong></td><td><span class="badge {{ $b->status }}">{{ ucfirst($b->status) }}</span></td><td><form class="update-form" method="POST" action="{{ route('lodge.update',$b) }}">@csrf @method('PATCH')<select name="status" aria-label="Status for {{ $b->reference }}"><option value="pending" @selected($b->status === 'pending')>Pending</option><option value="confirmed" @selected($b->status === 'confirmed')>Confirmed</option><option value="cancelled" @selected($b->status === 'cancelled')>Cancelled</option></select><button type="submit">Save</button></form></td></tr>@empty<tr><td colspan="7" class="empty-table">No bookings yet. New reservations will appear here.</td></tr>@endforelse</tbody></table></div><div class="pagination-area">{{ $bookings->appends(request()->query())->links() }}</div></section>
@if($section !== 'bookings')<div class="quick-grid"><a class="quick-card" href="{{ route('lodge.admin',['section'=>'bookings']) }}"><span class="quick-icon">▤</span><div><strong>Manage bookings</strong><small>Review and update reservations</small></div><span class="quick-arrow">→</span></a><a class="quick-card" href="{{ route('lodge.admin',['section'=>'photos']) }}"><span class="quick-icon">▧</span><div><strong>Lodge gallery</strong><small>Upload and organise photos</small></div><span class="quick-arrow">→</span></a></div>@endif
@endif
@endsection