Added booking approvals
This commit is contained in:
@@ -1 +0,0 @@
|
||||
package admin
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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..."
|
||||
|
||||
Reference in New Issue
Block a user