import { createFileRoute, Link } from '@tanstack/react-router' import { useEffect, useState } from 'react' import { m } from '@/i18n/messages' import { invoiceKind, invoiceStatus, emailKind } from '@/i18n/i18n-enum.gen' import { useLocale, getLocale } from '@/i18n/config' import { asI18n, type I18nNode } from '@pikku/react' import { useQueryClient } from '@tanstack/react-query' import { Alert, Badge, Button, Card, Container, Divider, Group, NumberInput, Paper, Select, SimpleGrid, Stack, Table, Tabs, Text, Textarea, TextInput, Title, } from '@pikku/mantine/core' import { usePikkuQuery, usePikkuMutation } from '@project/functions-sdk/pikku/api.gen' import { FileUploader, type UploadedAsset } from '@project/components' import { RoomsTab } from '../components/RoomsTab' import { ParticipantsTab } from '../components/ParticipantsTab' import { BookingStatusPanel } from '../components/BookingStatusPanel' import { BookingActions } from '../components/BookingActions' import { EventCard } from '../components/EventCard' import type { BookingStatus } from '../lib/status' import { fmtDate, toDateInputStr } from '../lib/dates' type Patch = { eventName?: string startDate?: string endDate?: string halfHouse?: boolean onlineAd?: boolean expectedPersons?: number | null eventOutline?: string | null description?: string | null coverImageUrl?: string | null organiserWebsite?: string | null mealTimeBreakfast?: string | null mealTimeLunch?: string | null mealTimeDinner?: string | null arrivalTime?: string | null departureTime?: string | null dailyPlan?: string | null notes?: string | null } function AdminBookingDetailInner() { useLocale() const { bookingId } = Route.useParams() const qc = useQueryClient() const queryArgs = { bookingId } const { data, isLoading, error, isFetching } = usePikkuQuery('getAdminBookingDetail', queryArgs, { refetchOnMount: 'always', }) const invalidate = async () => { await qc.invalidateQueries({ queryKey: ['getAdminBookingDetail', queryArgs] }) await qc.invalidateQueries({ queryKey: ['getBookingAuditLog', queryArgs] }) await qc.refetchQueries({ queryKey: ['getAdminBookingDetail', queryArgs], type: 'active' }) return qc.refetchQueries({ queryKey: ['getBookingAuditLog', queryArgs], type: 'active' }) } const updateMut = usePikkuMutation('updateAdminBooking', { onSuccess: invalidate }) const assignMut = usePikkuMutation('assignRoom', { onSuccess: invalidate }) const unassignMut = usePikkuMutation('unassignRoom', { onSuccess: invalidate }) const createInvoiceMut = usePikkuMutation('createInvoice', { onSuccess: invalidate }) const getUploadUrlMut = usePikkuMutation('getUploadUrl') const updateInvoiceMut = usePikkuMutation('updateInvoice', { onSuccess: invalidate }) const recordPaymentMut = usePikkuMutation('recordInvoicePayment', { onSuccess: invalidate }) const cancelInvoiceMut = usePikkuMutation('cancelInvoice', { onSuccess: invalidate }) const updateOrgMut = usePikkuMutation('updateClient', { onSuccess: invalidate }) const addParticipantMut = usePikkuMutation('addParticipant', { onSuccess: invalidate }) const updateParticipantMut = usePikkuMutation('updateParticipant', { onSuccess: invalidate }) const removeParticipantMut = usePikkuMutation('removeParticipant', { onSuccess: invalidate }) const addAllergyMut = usePikkuMutation('addParticipantAllergy', { onSuccess: invalidate }) const removeAllergyMut = usePikkuMutation('removeParticipantAllergy', { onSuccess: invalidate }) const today = new Date().toISOString().slice(0, 10) const [invDraft, setInvDraft] = useState({ kind: 'deposit' as 'deposit' | 'final', invoiceNumber: '', issuedOn: today, dueOn: '', amountCents: 0, pdfAssetKey: undefined as string | undefined, pdfName: '', }) const [patch, setPatch] = useState({}) const [saved, setSaved] = useState(false) const [isInvoicePdfUploading, setIsInvoicePdfUploading] = useState(false) const [invoiceUploadError, setInvoiceUploadError] = useState(false) const [orgPatch, setOrgPatch] = useState<{ name?: string contactEmail?: string | null contactPhone?: string | null billingAddress?: string | null website?: string | null }>({}) const [orgSaved, setOrgSaved] = useState(false) useEffect(() => { if (data) setPatch({}) }, [data?.booking.bookingId]) if (isLoading) return {m.common__states__loading()} if (error) return {m.common__states__error()} if (!data) return null const { booking, client, participants, rooms, invoices } = data const cur = (key: K, fallback: Patch[K]): Patch[K] => (key in patch ? patch[key] : fallback) const set = (key: K, value: Patch[K]) => { setPatch((p) => ({ ...p, [key]: value })) setSaved(false) } const requestInvoicePdfUpload = async (file: File) => getUploadUrlMut.mutateAsync({ purpose: 'invoicePdf', bookingId, fileName: file.name, contentType: file.type || 'application/pdf', size: file.size, }) const save = () => { if (Object.keys(patch).length === 0) return updateMut.mutate( { bookingId, patch }, { onSuccess: () => { setPatch({}) setSaved(true) }, }, ) } const unassignedParticipants = participants.filter((p) => !p.roomId) const organizationForm = ( {m.common__pages__admin_booking__client__title()} { const v = e.currentTarget.value setOrgPatch((p) => ({ ...p, name: v })) setOrgSaved(false) }} /> { const v = e.currentTarget.value setOrgPatch((p) => ({ ...p, contactEmail: v || null })) setOrgSaved(false) }} /> { const v = e.currentTarget.value setOrgPatch((p) => ({ ...p, contactPhone: v || null })) setOrgSaved(false) }} /> { const v = e.currentTarget.value setOrgPatch((p) => ({ ...p, website: v || null })) setOrgSaved(false) }} />