WIP call in booking
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import * as Modal from '$lib/components/ui/dialog';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Textarea } from '$lib/components/ui/textarea';
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
|
||||
export let open = false;
|
||||
export let initialUserId: string | null = null;
|
||||
|
||||
let userId = initialUserId ?? '';
|
||||
let startTime = new SvelteDate().toISOString().slice(0, 16); // datetime-local
|
||||
let serviceIds: string[] = [];
|
||||
let notes = '';
|
||||
|
||||
// You likely already have this elsewhere
|
||||
let services: Array<{ id: string; name: string }> = [];
|
||||
|
||||
async function loadServices() {
|
||||
const res = await fetch('/api/services');
|
||||
if (res.ok) services = await res.json();
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
if (!userId || serviceIds.length === 0) {
|
||||
toast.error('User and at least one service are required');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await fetch('/api/admin/bookings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_id: userId,
|
||||
start_time: new Date(startTime).toISOString(),
|
||||
service_ids: serviceIds,
|
||||
notes
|
||||
})
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
toast.error(text || 'Failed to create booking');
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success('Booking created and confirmed');
|
||||
open = false;
|
||||
}
|
||||
|
||||
$: if (open) loadServices();
|
||||
</script>
|
||||
|
||||
<Modal.Root bind:open>
|
||||
<Modal.Content class="max-w-lg">
|
||||
<Modal.Header>
|
||||
<Modal.Title>Create Booking</Modal.Title>
|
||||
</Modal.Header>
|
||||
|
||||
<div class="space-y-4 p-4">
|
||||
<div>
|
||||
<label class="text-xs text-gray-500">User ID</label>
|
||||
<Input bind:value={userId} placeholder="Paste or search user ID" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-xs text-gray-500">Start Time</label>
|
||||
<Input type="datetime-local" bind:value={startTime} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-xs text-gray-500">Services</label>
|
||||
<div class="space-y-2">
|
||||
{#each services as service}
|
||||
<label class="flex items-center gap-2 text-sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
value={service.id}
|
||||
onchange={(e) => {
|
||||
const checked = e.currentTarget.checked;
|
||||
serviceIds = checked
|
||||
? [...serviceIds, service.id]
|
||||
: serviceIds.filter((id) => id !== service.id);
|
||||
}}
|
||||
/>
|
||||
{service.name}
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-xs text-gray-500">Staff Notes</label>
|
||||
<Textarea rows={3} bind:value={notes} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal.Footer class="flex justify-end gap-2">
|
||||
<Button variant="outline" onclick={() => (open = false)}>Cancel</Button>
|
||||
<Button onclick={submit}>Create & Confirm</Button>
|
||||
</Modal.Footer>
|
||||
</Modal.Content>
|
||||
</Modal.Root>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Textarea } from '$lib/components/ui/textarea';
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
import BookingCreateModal from '$lib/components/admin/BookingCreateModal.svelte';
|
||||
|
||||
let showCreateModal = false;
|
||||
let selectedUserId: string | null = null;
|
||||
|
||||
function openForUser(userId: string) {
|
||||
selectedUserId = userId;
|
||||
showCreateModal = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-4">
|
||||
<h3 class="mb-2 text-sm font-semibold tracking-wide text-gray-600 uppercase">
|
||||
Call-In / Walk-In Booking
|
||||
</h3>
|
||||
|
||||
<p class="mb-4 text-sm text-gray-500">
|
||||
Create and confirm a booking immediately while speaking with the client.
|
||||
</p>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<Button onclick={() => (showCreateModal = true)}>Create Booking</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if showCreateModal}
|
||||
<BookingCreateModal bind:open={showCreateModal} initialUserId={selectedUserId} />
|
||||
{/if}
|
||||
@@ -24,6 +24,12 @@
|
||||
duration_minutes: number;
|
||||
};
|
||||
|
||||
function isPastAppointment(startTime: string, durationMinutes: number): boolean {
|
||||
const start = new Date(startTime);
|
||||
const end = new Date(start.getTime() + durationMinutes * 60_000);
|
||||
return end.getTime() < Date.now();
|
||||
}
|
||||
|
||||
let appointments = $state<TodayAppointment[]>([]);
|
||||
let loading = $state(true);
|
||||
|
||||
@@ -179,7 +185,8 @@
|
||||
<div class="space-y-3">
|
||||
{#each appointments as apt (apt.id)}
|
||||
<div
|
||||
class="flex items-center gap-4 rounded-lg border p-3 transition-all hover:shadow-md"
|
||||
class="flex items-center gap-4 rounded-lg border p-3 transition-all hover:shadow-md
|
||||
{isPastAppointment(apt.start_time, apt.duration_minutes) ? 'line-through opacity-50' : ''}"
|
||||
>
|
||||
<div class="min-w-[80px] text-sm font-semibold text-gray-700">
|
||||
{formatTime(apt.start_time)}
|
||||
|
||||
@@ -9,11 +9,9 @@
|
||||
import CurrentAppointment from '$lib/components/today/CurrentAppointment.svelte';
|
||||
import TodayCalendar from '$lib/components/today/TodayCalendar.svelte';
|
||||
import PendingApprovals from '$lib/components/today/PendingApprovals.svelte';
|
||||
// import QuickStats from '$lib/components/today/QuickStats.svelte';
|
||||
// import UpcomingArrivals from '$lib/components/today/UpcomingArrivals.svelte';
|
||||
// import RecentCheckouts from '$lib/components/today/RecentCheckouts.svelte';
|
||||
// import TodayRevenue from '$lib/components/today/TodayRevenue.svelte';
|
||||
// import LoyaltyRedemptions from '$lib/components/today/LoyaltyRedemptions.svelte';
|
||||
// import LoyaltyStats from '$lib/components/today/LoyaltyStats.svelte';
|
||||
import CallInBooking from '$lib/components/admin/CallInBooking.svelte';
|
||||
import BookingModal from '$lib/components/admin/BookingModal.svelte';
|
||||
import UserModal from '$lib/components/admin/UserModal.svelte';
|
||||
|
||||
@@ -109,6 +107,19 @@
|
||||
<!-- Current/Next Appointment Card (Full Width) -->
|
||||
<CurrentAppointment {openBookingModal} {openUserModal} />
|
||||
|
||||
<!-- quick booking Grid -->
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||
<!-- Left Column: Create a booking for a call in or messaging user (2/3 width on large screens) -->
|
||||
<div class="lg:col-span-2">
|
||||
<CallInBooking />
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Similar to the above but immediately block out my next availible working space while I talk over the walk-in users needs and chat etc (1/3 width on large screens) -->
|
||||
<div class="space-y-6">
|
||||
<!-- <WalkInBooking /> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content Grid -->
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||
<!-- Left Column: Today's Calendar (2/3 width on large screens) -->
|
||||
@@ -120,25 +131,16 @@
|
||||
<div class="space-y-6">
|
||||
<!-- 3 oldest pending appointments for approval -->
|
||||
<PendingApprovals {openBookingModal} />
|
||||
|
||||
<!-- Quick Stats -->
|
||||
<!-- <QuickStats /> -->
|
||||
|
||||
<!-- Upcoming Arrivals (Next 2 hours) -->
|
||||
<!-- <UpcomingArrivals {openBookingModal} {openUserModal} /> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Row: Additional Panels -->
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
<!-- Recent Checkouts -->
|
||||
<!-- <RecentCheckouts {openBookingModal} /> -->
|
||||
|
||||
<!-- Today's Revenue -->
|
||||
<!-- <TodayRevenue /> -->
|
||||
|
||||
<!-- Loyalty Redemptions Today -->
|
||||
<!-- <LoyaltyRedemptions {openUserModal} /> -->
|
||||
<!-- Loyalty point accumulations and redemptions today -->
|
||||
<!-- <LoyaltyStats {openUserModal} /> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user