feat(chatelet): guest booking UI — slice 1 (browse + availability) #142

Merged
padreug merged 4 commits from feat/chatelet-guest-ui into dev 2026-09-15 21:07:52 +00:00
Showing only changes of commit 65770ad9be - Show all commits

feat(chatelet): pick check-in/check-out with the shared DatePicker

Replace the two native <input type="date"> fields on the room detail
page with the base DatePicker (Popover + shadcn Calendar), the same
component the events editor uses. Check-in is floored at today and
check-out at the morning after check-in; moving check-in past the
current check-out clears it, and any change resets the last quote.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Padreug 2026-09-15 22:41:14 +02:00

View file

@ -1,13 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue' import { computed, onMounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { toast } from 'vue-sonner' import { toast } from 'vue-sonner'
import { addDays, format } from 'date-fns'
import { MapPin, Users } from 'lucide-vue-next' import { MapPin, Users } from 'lucide-vue-next'
import { Card, CardContent } from '@/components/ui/card' import { Card, CardContent } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
import DatePicker from '@/modules/base/components/DatePicker.vue'
import { useChatelet } from '../composables/useChatelet' import { useChatelet } from '../composables/useChatelet'
import type { AvailabilityResult, Room } from '../types/room' import type { AvailabilityResult, Room } from '../types/room'
@ -21,6 +22,26 @@ const checkOut = ref('')
const checking = ref(false) const checking = ref(false)
const result = ref<AvailabilityResult | null>(null) const result = ref<AvailabilityResult | null>(null)
// Same YYYY-MM-DD wire format the DatePicker speaks; the availability
// endpoint takes the same strings.
const today = computed(() => format(new Date(), 'yyyy-MM-dd'))
// Check-out must be at least the next morning. Bumping the floor also
// clears a check-out that the new check-in has overtaken.
const minCheckOut = computed(() =>
checkIn.value
? format(addDays(new Date(checkIn.value + 'T00:00:00'), 1), 'yyyy-MM-dd')
: today.value,
)
watch(checkIn, () => {
if (checkOut.value && checkOut.value < minCheckOut.value) checkOut.value = ''
result.value = null
})
watch(checkOut, () => {
result.value = null
})
onMounted(async () => { onMounted(async () => {
try { try {
room.value = await getRoom(route.params.id as string) room.value = await getRoom(route.params.id as string)
@ -86,13 +107,18 @@ async function onCheck() {
<Card> <Card>
<CardContent class="p-4 space-y-3"> <CardContent class="p-4 space-y-3">
<div class="grid grid-cols-2 gap-3"> <div class="grid grid-cols-2 gap-3">
<div class="space-y-1"> <div class="space-y-1 min-w-0">
<Label for="ci">Check-in</Label> <Label>Check-in</Label>
<Input id="ci" type="date" v-model="checkIn" /> <DatePicker v-model="checkIn" :min="today" placeholder="Check-in" />
</div> </div>
<div class="space-y-1"> <div class="space-y-1 min-w-0">
<Label for="co">Check-out</Label> <Label>Check-out</Label>
<Input id="co" type="date" v-model="checkOut" /> <DatePicker
v-model="checkOut"
:min="minCheckOut"
:disabled="!checkIn"
placeholder="Check-out"
/>
</div> </div>
</div> </div>
<Button <Button