feat: add hours, minutes, and price per hour fields to AdhocBookingTab; update booking price calculation
This commit is contained in:
@@ -39,7 +39,9 @@ function AdhocBookingTab({ facilities }: AdhocBookingTabProps) {
|
|||||||
|
|
||||||
const [facilityId, setFacilityId] = React.useState(adhocFacilities[0]?.facilityId ?? "")
|
const [facilityId, setFacilityId] = React.useState(adhocFacilities[0]?.facilityId ?? "")
|
||||||
const [startTime, setStartTime] = React.useState(() => toLocalInputValue(new Date()))
|
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 [partySize, setPartySize] = React.useState("")
|
||||||
const [guestName, setGuestName] = React.useState("")
|
const [guestName, setGuestName] = React.useState("")
|
||||||
const [guestPhone, setGuestPhone] = React.useState("")
|
const [guestPhone, setGuestPhone] = React.useState("")
|
||||||
@@ -49,13 +51,19 @@ function AdhocBookingTab({ facilities }: AdhocBookingTabProps) {
|
|||||||
const { busy: checking, run: runCheck } = useMutation()
|
const { busy: checking, run: runCheck } = useMutation()
|
||||||
const { busy: booking, run: runBook } = useMutation()
|
const { busy: booking, run: runBook } = useMutation()
|
||||||
|
|
||||||
const selectedFacility = adhocFacilities.find((f) => f.facilityId === facilityId)
|
const durationMinutes = (Number(hours) || 0) * 60 + (Number(minutes) || 0)
|
||||||
const durationMinutes = Number(duration) || 0
|
const bookingPrice = ((Number(pricePerHour) || 0) * durationMinutes) / 60
|
||||||
const estimatedPrice = selectedFacility ? (selectedFacility.pricePerHour * durationMinutes) / 60 : 0
|
|
||||||
|
|
||||||
const facilityOptions = adhocFacilities.map((f) => ({ label: f.name, value: f.facilityId }))
|
const facilityOptions = adhocFacilities.map((f) => ({ label: f.name, value: f.facilityId }))
|
||||||
const canCheck = !!facilityId && !!startTime && durationMinutes >= 1 && !checking
|
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() {
|
async function handleCheckAvailability() {
|
||||||
if (!canCheck) return
|
if (!canCheck) return
|
||||||
@@ -87,6 +95,7 @@ function AdhocBookingTab({ facilities }: AdhocBookingTabProps) {
|
|||||||
guestName: guestName.trim(),
|
guestName: guestName.trim(),
|
||||||
guestPhone: guestPhone.trim(),
|
guestPhone: guestPhone.trim(),
|
||||||
partySize: partySize ? Number(partySize) : undefined,
|
partySize: partySize ? Number(partySize) : undefined,
|
||||||
|
pricePerHour: Number(pricePerHour),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
if (bookingResult.shareUrl) {
|
if (bookingResult.shareUrl) {
|
||||||
@@ -125,14 +134,7 @@ function AdhocBookingTab({ facilities }: AdhocBookingTabProps) {
|
|||||||
<Label>
|
<Label>
|
||||||
Facility <span className="text-destructive">*</span>
|
Facility <span className="text-destructive">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<Select
|
<Select items={facilityOptions} value={facilityId} onValueChange={(v) => selectFacility(v ?? "")}>
|
||||||
items={facilityOptions}
|
|
||||||
value={facilityId}
|
|
||||||
onValueChange={(v) => {
|
|
||||||
setFacilityId(v ?? "")
|
|
||||||
setAvailability(null)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="w-full">
|
<SelectTrigger className="w-full">
|
||||||
<SelectValue placeholder="Select facility" />
|
<SelectValue placeholder="Select facility" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
@@ -146,7 +148,6 @@ function AdhocBookingTab({ facilities }: AdhocBookingTabProps) {
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<Label htmlFor="adhoc-start">
|
<Label htmlFor="adhoc-start">
|
||||||
Start <span className="text-destructive">*</span>
|
Start <span className="text-destructive">*</span>
|
||||||
@@ -162,25 +163,65 @@ function AdhocBookingTab({ facilities }: AdhocBookingTabProps) {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<Label htmlFor="adhoc-duration">
|
<Label>
|
||||||
Duration (minutes) <span className="text-destructive">*</span>
|
Duration <span className="text-destructive">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
<Input
|
<Input
|
||||||
id="adhoc-duration"
|
|
||||||
type="number"
|
type="number"
|
||||||
min={1}
|
min={0}
|
||||||
value={duration}
|
aria-label="Hours"
|
||||||
|
value={hours}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setDuration(e.target.value)
|
setHours(e.target.value)
|
||||||
setAvailability(null)
|
setAvailability(null)
|
||||||
}}
|
}}
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
|
<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>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
<div className="flex flex-col gap-1.5">
|
||||||
|
<Label htmlFor="adhoc-price-per-hour">
|
||||||
|
Price / Hour (Rs.) <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="adhoc-price-per-hour"
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
step="0.01"
|
||||||
|
value={pricePerHour}
|
||||||
|
onChange={(e) => setPricePerHour(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-1.5">
|
||||||
|
<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. {bookingPrice.toLocaleString(undefined, { maximumFractionDigits: 2 })}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<Label htmlFor="adhoc-party-size">Party Size</Label>
|
<Label htmlFor="adhoc-party-size">Party Size</Label>
|
||||||
<Input
|
<Input
|
||||||
@@ -192,13 +233,6 @@ function AdhocBookingTab({ facilities }: AdhocBookingTabProps) {
|
|||||||
onChange={(e) => setPartySize(e.target.value)}
|
onChange={(e) => setPartySize(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1.5">
|
|
||||||
<Label>Estimated Price</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 })}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-end gap-3">
|
<div className="flex items-end gap-3">
|
||||||
<Button type="button" variant="outline" onClick={handleCheckAvailability} disabled={!canCheck}>
|
<Button type="button" variant="outline" onClick={handleCheckAvailability} disabled={!canCheck}>
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ export interface CreateGuestAdhocBookingInput {
|
|||||||
guestName: string
|
guestName: string
|
||||||
guestPhone: string
|
guestPhone: string
|
||||||
partySize?: number
|
partySize?: number
|
||||||
|
/** Overrides the facility's own price per hour for this booking's charge only. */
|
||||||
|
pricePerHour?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ReassignBookingInput {
|
export interface ReassignBookingInput {
|
||||||
|
|||||||
Reference in New Issue
Block a user