import { createFileRoute, useNavigate } from '@tanstack/react-router'
import { useMemo, useState } from 'react'
import { m, mKey } from '@/i18n/messages'
import { useLocale, getLocale } from '@/i18n/config'
import { asI18n } from '@pikku/react'
import { keepPreviousData, useQueryClient } from '@tanstack/react-query'
import { useDebouncedValue } from '@mantine/hooks'
import {
Alert,
Anchor,
Badge,
Box,
Button,
Container,
Group,
Loader,
Modal,
MultiSelect,
Paper,
SegmentedControl,
Select,
SimpleGrid,
Stack,
Table,
Text,
TextInput,
} from '@pikku/mantine/core'
import { Check, Clock, X } from 'lucide-react'
import { usePikkuMutation, usePikkuQuery } from '@project/functions-sdk/pikku/api.gen'
import { usePageTitle } from '../components/AppLayout'
import { RequireAuth } from '../auth'
import { fmtDate, fmtDateShort } from '../lib/dates'
const STATUS_KEYS = ['pending', 'approved', 'declined'] as const
type EnquiryStatus = (typeof STATUS_KEYS)[number]
type EnquiryDateOption = {
optionId: string
startDate: string
endDate: string
priority: number
}
type Enquiry = {
enquiryId: string
contactName: string | null
contactEmail: string
contactPhone: string | null
website: string | null
eventName: string
eventOutline: string | null
description: string | null
dateOptions: EnquiryDateOption[]
expectedPersons: number | null
halfHouse: boolean
notes: string | null
status: string
waitlistedAt: Date | null
approvedAt: Date | null
declinedAt: Date | null
createdAt: Date
bookingId: string | null
}
// The SDK's date reviver (transform-date.js) turns any 'YYYY-MM-DD…' response
// string into a Date — including these option dates, despite their string type.
// Normalize back to a plain 'YYYY-MM-DD' string so the date inputs and equality
// checks below work, and parse at local midnight for display.
const toYmd = (v: string | Date): string =>
v instanceof Date ? v.toISOString().slice(0, 10) : v
const optDate = (s: string) => new Date(`${s}T00:00:00`)
function StatusBadge({ enquiry }: { enquiry: Enquiry }) {
useLocale()
const color =
enquiry.status === 'approved' ? 'green' : enquiry.status === 'declined' ? 'red' : 'blue'
return (
{mKey(`common.pages.admin.enquiries.status.${enquiry.status}` as any)}
{enquiry.status === 'pending' && enquiry.waitlistedAt && (
{m.common_pages_admin_enquiries_status_waitlisted()}
)}
)
}
function AdminEnquiriesInner() {
useLocale()
const queryClient = useQueryClient()
const navigate = useNavigate()
usePageTitle(m.common_pages_admin_enquiries_title())
const [statusFilter, setStatusFilter] = useState(['pending'])
const queryArgs = useMemo(
() => ({
venueSlug: 'drawehn',
status: statusFilter.length ? statusFilter : undefined,
}),
[statusFilter],
)
const { data, isLoading, isFetching, error } = usePikkuQuery('getEnquiries', queryArgs, {
placeholderData: keepPreviousData,
})
const invalidate = () => queryClient.invalidateQueries({ queryKey: ['getEnquiries'] })
// Which enquiry has an open transition modal, and which kind.
const [modal, setModal] = useState<{ kind: 'approve' | 'waitlist' | 'decline'; enquiry: Enquiry } | null>(null)
const statusOptions = useMemo(
() =>
STATUS_KEYS.map((s) => ({
value: s,
label: mKey(`common.pages.admin.enquiries.status.${s}` as any),
})),
[getLocale()],
)
if (isLoading && !data) {
return {m.common_states_loading()}
}
if (error && !data) {
return {m.common_states_error()}
}
if (!data) return null
const enquiries = (data.enquiries as Enquiry[]).map((e) => ({
...e,
dateOptions: e.dateOptions.map((o) => ({
...o,
startDate: toYmd(o.startDate),
endDate: toYmd(o.endDate),
})),
}))
return (
{m.common_pages_admin_enquiries_stats_showing({ count: enquiries.length })}
{isFetching && }
{enquiries.length === 0 ? (
{m.common_pages_admin_enquiries_empty()}
) : (
{m.common_pages_admin_enquiries_cols_received()}
{m.common_pages_admin_enquiries_cols_event()}
{m.common_pages_admin_enquiries_cols_contact()}
{m.common_pages_admin_enquiries_cols_dates()}
{m.common_pages_admin_enquiries_cols_persons()}
{m.common_pages_admin_enquiries_cols_status()}
{m.common_pages_admin_enquiries_cols_actions()}
{enquiries.map((e) => (
{asI18n(`${fmtDateShort(e.createdAt, getLocale())}`)}
{asI18n(`${e.eventName}`)}
{e.eventOutline && (
{asI18n(`${e.eventOutline}`)}
)}
{asI18n(`${e.contactName ?? '—'}`)}
{asI18n(`${e.contactEmail}`)}
{e.dateOptions.length === 0 ? (
{m.common_pages_admin_enquiries_no_dates()}
) : (
{e.dateOptions.map((o, i) => (
{asI18n(`${fmtDateShort(optDate(o.startDate), getLocale())} → ${fmtDateShort(optDate(o.endDate), getLocale())}`)}
))}
)}
{e.expectedPersons ?? '—'}
{e.status === 'pending' ? (
}
onClick={() => setModal({ kind: 'approve', enquiry: e })}
>
{m.common_pages_admin_enquiries_actions_approve()}
}
onClick={() => setModal({ kind: 'waitlist', enquiry: e })}
>
{m.common_pages_admin_enquiries_actions_waitlist()}
}
onClick={() => setModal({ kind: 'decline', enquiry: e })}
>
{m.common_pages_admin_enquiries_actions_decline()}
) : e.status === 'approved' && e.bookingId ? (
navigate({ to: '/admin/bookings/$bookingId', params: { bookingId: e.bookingId! } })
}
>
{m.common_pages_admin_enquiries_actions_view_booking()}
) : (
{asI18n('—')}
)}
))}
)}
{modal?.kind === 'approve' && (
setModal(null)} onDone={() => { setModal(null); invalidate() }} />
)}
{modal?.kind === 'waitlist' && (
setModal(null)} onDone={() => { setModal(null); invalidate() }} />
)}
{modal?.kind === 'decline' && (
setModal(null)} onDone={() => { setModal(null); invalidate() }} />
)}
)
}
// ─── Approve modal ──────────────────────────────────────────────────────────
function ApproveModal({ enquiry, onClose, onDone }: { enquiry: Enquiry; onClose: () => void; onDone: () => void }) {
useLocale()
const [mode, setMode] = useState<'existing' | 'new'>('existing')
// Prefill from the visitor's preferred (first) date option; the admin can
// pick a different option or override the inputs entirely.
const topOption = enquiry.dateOptions[0]
const [startDate, setStartDate] = useState(topOption?.startDate ?? '')
const [endDate, setEndDate] = useState(topOption?.endDate ?? '')
// Existing-client search (server-side via getClients).
const [clientId, setClientId] = useState(null)
const [search, setSearch] = useState('')
const [debouncedSearch] = useDebouncedValue(search, 250)
const { data: clientData, isFetching: clientsFetching } = usePikkuQuery(
'getClients',
{ venueSlug: 'drawehn', q: debouncedSearch.trim() || undefined },
{ placeholderData: keepPreviousData, enabled: mode === 'existing' },
)
const clientOptions = useMemo(
() =>
(clientData?.clients ?? []).map((c) => ({
value: c.clientId,
label: c.name ? `${c.name}${c.contactEmail ? ` · ${c.contactEmail}` : ''}` : (c.contactEmail ?? c.clientId),
})),
[clientData],
)
// New-client fields, pre-filled from the enquiry contact.
const [name, setName] = useState(enquiry.contactName ?? '')
const [email, setEmail] = useState(enquiry.contactEmail)
const [phone, setPhone] = useState(enquiry.contactPhone ?? '')
const [website, setWebsite] = useState(enquiry.website ?? '')
const [billingAddress, setBillingAddress] = useState('')
const mutation = usePikkuMutation('approveEnquiry', { onSuccess: onDone })
const datesValid = !!startDate && !!endDate && startDate <= endDate
const clientValid = mode === 'existing' ? !!clientId : !!email.trim()
const canSubmit = datesValid && clientValid
const submit = () => {
mutation.mutate({
enquiryId: enquiry.enquiryId,
startDate,
endDate,
client:
mode === 'existing'
? { clientId: clientId! }
: {
name: name.trim() || undefined,
contactEmail: email.trim() || undefined,
contactPhone: phone.trim() || undefined,
website: website.trim() || undefined,
billingAddress: billingAddress.trim() || undefined,
},
})
}
return (
{asI18n(`${enquiry.eventName}`)}
{enquiry.dateOptions.length > 0 && (
{m.common_pages_admin_enquiries_approve_modal_date_options()}
{enquiry.dateOptions.map((o, i) => {
const selected = o.startDate === startDate && o.endDate === endDate
return (
{
setStartDate(o.startDate)
setEndDate(o.endDate)
}}
>
{asI18n(`${i + 1}. ${fmtDateShort(optDate(o.startDate), getLocale())} → ${fmtDateShort(optDate(o.endDate), getLocale())}`)}
)
})}
)}
setStartDate(e.currentTarget.value)}
/>
setEndDate(e.currentTarget.value)}
error={startDate && endDate && startDate > endDate ? m.common_pages_admin_enquiries_approve_modal_date_error() : undefined}
/>
setMode(v as 'existing' | 'new')}
data={[
{ value: 'existing', label: m.common_pages_admin_enquiries_approve_modal_existing_client() },
{ value: 'new', label: m.common_pages_admin_enquiries_approve_modal_new_client() },
]}
/>
{mode === 'existing' ? (
: undefined}
nothingFoundMessage={m.common_pages_admin_enquiries_approve_modal_no_clients()}
filter={({ options }) => options}
/>
) : (
setName(e.currentTarget.value)}
/>
setEmail(e.currentTarget.value)}
/>
setPhone(e.currentTarget.value)}
/>
setWebsite(e.currentTarget.value)}
/>
setBillingAddress(e.currentTarget.value)}
/>
)}
{mutation.error && (
{asI18n(`${mutation.error.message || m.common_pages_admin_enquiries_transition_error()}`)}
)}
)
}
// ─── Waitlist modal ─────────────────────────────────────────────────────────
function WaitlistModal({ enquiry, onClose, onDone }: { enquiry: Enquiry; onClose: () => void; onDone: () => void }) {
useLocale()
const mutation = usePikkuMutation('waitlistEnquiry', { onSuccess: onDone })
const hasOptions = enquiry.dateOptions.length > 0
return (
{m.common_pages_admin_enquiries_waitlist_modal_intro()}
{hasOptions ? (
{enquiry.dateOptions.map((o, i) => (
{asI18n(`${i + 1}. ${fmtDateShort(optDate(o.startDate), getLocale())} → ${fmtDateShort(optDate(o.endDate), getLocale())}`)}
))}
) : (
{m.common_pages_admin_enquiries_waitlist_modal_no_options()}
)}
{mutation.error && (
{asI18n(`${mutation.error.message || m.common_pages_admin_enquiries_transition_error()}`)}
)}
)
}
// ─── Decline modal ──────────────────────────────────────────────────────────
function DeclineModal({ enquiry, onClose, onDone }: { enquiry: Enquiry; onClose: () => void; onDone: () => void }) {
useLocale()
const mutation = usePikkuMutation('declineEnquiry', { onSuccess: onDone })
return (
{m.common_pages_admin_enquiries_decline_modal_confirm({
event: enquiry.eventName,
date: enquiry.createdAt ? fmtDate(enquiry.createdAt, getLocale()) : '',
})}
{mutation.error && (
{asI18n(`${mutation.error.message || m.common_pages_admin_enquiries_transition_error()}`)}
)}
)
}
function AdminEnquiriesPage() {
return (
)
}
export const Route = createFileRoute('/admin/enquiries')({
component: AdminEnquiriesPage,
})