Added booking approvals

This commit is contained in:
2026-01-19 22:32:25 +00:00
parent 73610e5d8e
commit 1dd37a8d22
7 changed files with 110 additions and 50 deletions
-1
View File
@@ -1 +0,0 @@
package admin
+4 -1
View File
@@ -771,6 +771,7 @@ func GetAdminBookingHandler(w http.ResponseWriter, r *http.Request) {
// ----------------------------
serviceRows, err := db.DB.Query(r.Context(), `
SELECT
bs.service_id,
s.name,
coalesce(bs.override_price, s.price) as price,
coalesce(bs.override_duration_minutes, s.duration_minutes) as duration_minutes
@@ -790,11 +791,12 @@ func GetAdminBookingHandler(w http.ResponseWriter, r *http.Request) {
var durationMinutesTotal int
for serviceRows.Next() {
var serviceID string
var name string
var price float64
var durationMinutes int
if err := serviceRows.Scan(&name, &price, &durationMinutes); err != nil {
if err := serviceRows.Scan(&serviceID, &name, &price, &durationMinutes); err != nil {
log.Printf("Failed to scan service for booking %s: %v", bookingID, err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
@@ -804,6 +806,7 @@ func GetAdminBookingHandler(w http.ResponseWriter, r *http.Request) {
totalAmount += price
durationMinutesTotal += durationMinutes
booking.Services = append(booking.Services, BookingService{
ServiceID: serviceID, // now set
ServiceName: &name,
Price: &price,
DurationMinutes: &durationMinutes,
+7
View File
@@ -20,6 +20,7 @@ import (
"crussell/handlers/notifications"
"crussell/handlers/scheduling"
"crussell/handlers/services"
"crussell/handlers/today"
"crussell/handlers/user"
)
@@ -140,6 +141,12 @@ func main() {
r.Get("/{id}", user.GetAdminUserHandler)
})
r.Route("/admin/today", func(r chi.Router) {
r.Get("/current-next", today.GetCurrentAndNextHandler)
r.Get("/appointments", today.GetTodayAppointmentsHandler)
r.Get("/pending-approvals", today.GetPendingApprovalsHandler)
})
// --- Admin Notifications ---
r.Route("/admin/notifications", func(r chi.Router) {
// List all unacknowledged notifications
+1 -1
View File
@@ -42,7 +42,7 @@
"svelte-sonner": "^1.0.5",
"sveltekit-superforms": "^2.27.1",
"tailwind-merge": "^3.3.1",
"tailwind-variants": "^3.1.1",
"tailwind-variants": "^3.2.2",
"tailwindcss": "^4.0.0",
"tw-animate-css": "^1.3.8",
"typescript": "^5.0.0",
@@ -4,6 +4,7 @@
import { toast } from 'svelte-sonner';
import * as Modal from '$lib/components/ui/dialog';
import { Button } from '$lib/components/ui/button';
import ApprovalModal from '$lib/components/admin/ApprovalModal.svelte';
interface Props {
open: boolean;
@@ -31,6 +32,8 @@
user?: {
id: string;
first_name: string;
last_name: string;
full_name: string;
email?: string;
phone?: string;
@@ -80,6 +83,13 @@
};
let selectedBooking = $state<Booking | null>(null);
let showApprovalModal = $state(false);
// Calculate total duration from services
let totalDuration = $derived(
selectedBooking?.services?.reduce((sum, service) => sum + (service.duration_minutes || 0), 0) ||
0
);
async function fetchBookingDetails() {
if (!bookingId) return;
@@ -104,6 +114,8 @@
user: data.user
? {
id: data.user.id,
first_name: data.user.first_name,
last_name: data.user.last_name,
full_name: data.user.full_name,
email: data.user.email,
phone: data.user.phone,
@@ -178,28 +190,35 @@
{/if}
</div>
{#if selectedBooking}
<span
class="inline-flex items-center rounded-full px-3 py-1 text-sm font-medium
{selectedBooking.status === 'pending'
? 'bg-yellow-100 text-yellow-800'
: selectedBooking.status === 'confirmed'
? 'bg-emerald-100 text-emerald-800'
: selectedBooking.status === 'in_progress'
? 'bg-blue-100 text-blue-800'
: selectedBooking.status === 'completed'
? 'bg-green-100 text-green-800'
: selectedBooking.status === 'client_cancelled'
? 'bg-red-100 text-red-800'
: selectedBooking.status === 'we_cancelled'
? 'bg-rose-100 text-rose-800'
: selectedBooking.status === 're-schedule'
? 'bg-purple-100 text-purple-800'
: selectedBooking.status === 'no_show'
? 'bg-gray-100 text-gray-800'
: 'bg-gray-100 text-gray-800'}"
>
{selectedBooking.status.charAt(0).toUpperCase() + selectedBooking.status.slice(1)}
</span>
{#if selectedBooking.status === 'pending'}
<div class="flex items-center gap-2">
<Button
onclick={() => (showApprovalModal = true)}
class="bg-emerald-600 hover:bg-emerald-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="mr-2 h-4 w-4"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clip-rule="evenodd"
/>
</svg>
Approve Booking
</Button>
<span
class="inline-flex items-center rounded-full bg-yellow-100 px-3 py-1 text-sm
font-medium text-yellow-800"
>
Pending
</span>
</div>
{/if}
{/if}
</div>
</Modal.Header>
@@ -233,7 +252,7 @@
</div>
<div>
<div class="text-xs text-gray-500">Duration</div>
<div class="font-medium">{selectedBooking.duration_minutes} minutes</div>
<div class="font-medium">{totalDuration} minutes</div>
</div>
<div>
<div class="text-xs text-gray-500">Created</div>
@@ -262,6 +281,29 @@
{/if}
</div>
<!-- Services -->
{#if selectedBooking.services && selectedBooking.services.length > 0}
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
<h3 class="mb-3 text-sm font-semibold tracking-wide text-gray-600 uppercase">
Services
</h3>
<div class="space-y-3">
{#each selectedBooking.services as service, index (index)}
<div class="rounded-md border border-gray-300 bg-white p-3">
<div class="font-medium">{service.service_name || '—'}</div>
{#if service.service_description}
<div class="mt-1 text-sm text-gray-600">{service.service_description}</div>
{/if}
<div class="mt-2 flex items-center justify-between text-sm">
<span class="text-gray-600">{service.duration_minutes} min</span>
<span class="font-semibold">£{service.price?.toFixed(2) || '0.00'}</span>
</div>
</div>
{/each}
</div>
</div>
{/if}
<!-- Customer Information -->
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
<h3 class="mb-3 text-sm font-semibold tracking-wide text-gray-600 uppercase">
@@ -311,29 +353,6 @@
{/if}
</div>
<!-- Services -->
{#if selectedBooking.services && selectedBooking.services.length > 0}
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
<h3 class="mb-3 text-sm font-semibold tracking-wide text-gray-600 uppercase">
Services
</h3>
<div class="space-y-3">
{#each selectedBooking.services as service, index (index)}
<div class="rounded-md border border-gray-300 bg-white p-3">
<div class="font-medium">{service.service_name || '—'}</div>
{#if service.service_description}
<div class="mt-1 text-sm text-gray-600">{service.service_description}</div>
{/if}
<div class="mt-2 flex items-center justify-between text-sm">
<span class="text-gray-600">{service.duration_minutes} min</span>
<span class="font-semibold">£{service.price?.toFixed(2) || '0.00'}</span>
</div>
</div>
{/each}
</div>
</div>
{/if}
<!-- Financial Summary -->
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
<h3 class="mb-3 text-sm font-semibold tracking-wide text-gray-600 uppercase">
@@ -435,3 +454,15 @@
</Modal.Footer>
</Modal.Content>
</Modal.Root>
<!-- Approval Sub-Modal -->
{#if selectedBooking && showApprovalModal}
<ApprovalModal
bind:open={showApprovalModal}
booking={selectedBooking}
onApproved={() => {
showApprovalModal = false;
fetchBookingDetails();
}}
/>
{/if}
+1 -1
View File
@@ -711,7 +711,7 @@
<li> Share your referral code with friends</li>
<li> They get 10% off their first booking</li>
<li> You get 10% off your next booking after they claim</li>
<li> You earn 1 loyalty stamp for each use to keep the savings going</li>
<li> You earn 3 loyalty stamp for each use to keep the savings going</li>
</ul>
</div>
</Card.Content>
+20
View File
@@ -262,6 +262,26 @@ format_london_time() {
TZ=Europe/London date -d "$DATE_PART $TIME_PART" +"%Y-%m-%dT%H:%M:%S%:z"
}
# --- TODAY BOOKINGS (4 bookings) ---
echo ""
echo "Creating 4 bookings for today..."
TODAY_DATE=$(TZ=Europe/London date +%Y-%m-%d)
create_booking "$USER_TOKEN" "$(format_london_time "$TODAY_DATE" "09:30:00")" \
"[\"${SERVICE_IDS[0]}\"]" "" "Today - Classic Manicure 09:30"
create_booking "$USER_TOKEN" "$(format_london_time "$TODAY_DATE" "11:00:00")" \
"[\"${SERVICE_IDS[1]}\"]" "" "Today - Gel Manicure 11:00"
create_booking "$USER_TOKEN" "$(format_london_time "$TODAY_DATE" "13:30:00")" \
"[\"${SERVICE_IDS[3]}\"]" "Lunch break slot" "Today - Express Mani & Pedi 13:30"
create_booking "$USER_TOKEN" "$(format_london_time "$TODAY_DATE" "16:00:00")" \
"[\"${SERVICE_IDS[0]}\",\"${SERVICE_IDS[5]}\"]" \
"End of day treat" "Today - Classic + Nail Art 16:00"
# --- PAST BOOKINGS (35 bookings from October-November 2025) ---
echo ""
echo "Creating 35 past bookings..."