Feat/booking new 0928 #1

Merged
ashan_rusiru merged 3 commits from feat/booking-new-0928 into dev 2026-10-06 16:19:17 +05:30
2 changed files with 88 additions and 52 deletions
Showing only changes of commit 0ba1e6a9e6 - Show all commits
+86 -52
View File
@@ -39,7 +39,9 @@ function AdhocBookingTab({ facilities }: AdhocBookingTabProps) {
const [facilityId, setFacilityId] = React.useState(adhocFacilities[0]?.facilityId ?? "")
const [startTime, setStartTime] = React.useState(() => toLocalInputValue(new Date()))
const [duration, setDuration] = React.useState("60")
const [hours, setHours] = React.useState("1")
const [minutes, setMinutes] = React.useState("0")
const [pricePerHour, setPricePerHour] = React.useState(() => String(adhocFacilities[0]?.pricePerHour ?? 0))
const [partySize, setPartySize] = React.useState("")
const [guestName, setGuestName] = React.useState("")
const [guestPhone, setGuestPhone] = React.useState("")
@@ -49,13 +51,19 @@ function AdhocBookingTab({ facilities }: AdhocBookingTabProps) {
const { busy: checking, run: runCheck } = useMutation()
const { busy: booking, run: runBook } = useMutation()
const selectedFacility = adhocFacilities.find((f) => f.facilityId === facilityId)
const durationMinutes = Number(duration) || 0
const estimatedPrice = selectedFacility ? (selectedFacility.pricePerHour * durationMinutes) / 60 : 0
const durationMinutes = (Number(hours) || 0) * 60 + (Number(minutes) || 0)
const bookingPrice = ((Number(pricePerHour) || 0) * durationMinutes) / 60
const facilityOptions = adhocFacilities.map((f) => ({ label: f.name, value: f.facilityId }))
const canCheck = !!facilityId && !!startTime && durationMinutes >= 1 && !checking
const canSubmit = canCheck && !!guestName.trim() && !!guestPhone.trim() && !booking
const canSubmit = canCheck && Number(pricePerHour) >= 0 && !!guestName.trim() && !!guestPhone.trim() && !booking
function selectFacility(id: string) {
setFacilityId(id)
setAvailability(null)
const facility = adhocFacilities.find((f) => f.facilityId === id)
setPricePerHour(String(facility?.pricePerHour ?? 0))
}
async function handleCheckAvailability() {
if (!canCheck) return
@@ -87,6 +95,7 @@ function AdhocBookingTab({ facilities }: AdhocBookingTabProps) {
guestName: guestName.trim(),
guestPhone: guestPhone.trim(),
partySize: partySize ? Number(partySize) : undefined,
pricePerHour: Number(pricePerHour),
})
)
if (bookingResult.shareUrl) {
@@ -125,14 +134,7 @@ function AdhocBookingTab({ facilities }: AdhocBookingTabProps) {
<Label>
Facility <span className="text-destructive">*</span>
</Label>
<Select
items={facilityOptions}
value={facilityId}
onValueChange={(v) => {
setFacilityId(v ?? "")
setAvailability(null)
}}
>
<Select items={facilityOptions} value={facilityId} onValueChange={(v) => selectFacility(v ?? "")}>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select facility" />
</SelectTrigger>
@@ -146,60 +148,92 @@ function AdhocBookingTab({ facilities }: AdhocBookingTabProps) {
</Select>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="flex flex-col gap-1.5">
<Label htmlFor="adhoc-start">
Start <span className="text-destructive">*</span>
</Label>
<Input
id="adhoc-start"
type="datetime-local"
value={startTime}
onChange={(e) => {
setStartTime(e.target.value)
setAvailability(null)
}}
required
/>
</div>
<div className="flex flex-col gap-1.5">
<Label htmlFor="adhoc-duration">
Duration (minutes) <span className="text-destructive">*</span>
</Label>
<Input
id="adhoc-duration"
type="number"
min={1}
value={duration}
onChange={(e) => {
setDuration(e.target.value)
setAvailability(null)
}}
required
/>
<div className="flex flex-col gap-1.5">
<Label htmlFor="adhoc-start">
Start <span className="text-destructive">*</span>
</Label>
<Input
id="adhoc-start"
type="datetime-local"
value={startTime}
onChange={(e) => {
setStartTime(e.target.value)
setAvailability(null)
}}
required
/>
</div>
<div className="flex flex-col gap-1.5">
<Label>
Duration <span className="text-destructive">*</span>
</Label>
<div className="grid grid-cols-2 gap-3">
<div className="flex items-center gap-2">
<Input
type="number"
min={0}
aria-label="Hours"
value={hours}
onChange={(e) => {
setHours(e.target.value)
setAvailability(null)
}}
/>
<span className="text-sm text-muted-foreground">hours</span>
</div>
<div className="flex items-center gap-2">
<Input
type="number"
min={0}
max={59}
aria-label="Minutes"
value={minutes}
onChange={(e) => {
setMinutes(e.target.value)
setAvailability(null)
}}
/>
<span className="text-sm text-muted-foreground">minutes</span>
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="flex flex-col gap-1.5">
<Label htmlFor="adhoc-party-size">Party Size</Label>
<Label htmlFor="adhoc-price-per-hour">
Price / Hour (Rs.) <span className="text-destructive">*</span>
</Label>
<Input
id="adhoc-party-size"
id="adhoc-price-per-hour"
type="number"
min={1}
placeholder="Optional headcount"
value={partySize}
onChange={(e) => setPartySize(e.target.value)}
min={0}
step="0.01"
value={pricePerHour}
onChange={(e) => setPricePerHour(e.target.value)}
required
/>
</div>
<div className="flex flex-col gap-1.5">
<Label>Estimated Price</Label>
<Label>Price for Booking</Label>
<div className="flex h-9 items-center rounded-md border bg-muted/40 px-3 text-sm font-medium">
Rs. {estimatedPrice.toLocaleString(undefined, { maximumFractionDigits: 2 })}
Rs. {bookingPrice.toLocaleString(undefined, { maximumFractionDigits: 2 })}
</div>
</div>
</div>
<div className="flex flex-col gap-1.5">
<Label htmlFor="adhoc-party-size">Party Size</Label>
<Input
id="adhoc-party-size"
type="number"
min={1}
placeholder="Optional headcount"
value={partySize}
onChange={(e) => setPartySize(e.target.value)}
/>
</div>
<div className="flex items-end gap-3">
<Button type="button" variant="outline" onClick={handleCheckAvailability} disabled={!canCheck}>
{checking ? <Spinner className="size-4" /> : <SearchIcon />}
+2
View File
@@ -67,6 +67,8 @@ export interface CreateGuestAdhocBookingInput {
guestName: string
guestPhone: string
partySize?: number
/** Overrides the facility's own price per hour for this booking's charge only. */
pricePerHour?: number
}
export interface ReassignBookingInput {