chore: seminarhof customer project

This commit is contained in:
e2e
2026-07-11 08:29:46 +02:00
commit 1d847171eb
188 changed files with 20784 additions and 0 deletions

View File

@@ -0,0 +1,157 @@
import { Link, createFileRoute } from '@tanstack/react-router'
import { m } from '@/i18n/messages'
import { bookingStatus } from '@/i18n/i18n-enum.gen'
import { useLocale, getLocale } from '@/i18n/config'
import { asI18n } from '@pikku/react'
import { Box, Container, Group, Paper, Stack, Text } from '@pikku/mantine/core'
import { usePikkuQuery } from '@project/functions-sdk/pikku/api.gen'
import { usePageTitle } from '../components/AppLayout'
import { PublicShell } from '../components/PublicShell'
import { BOOKING_STATUS, type BookingStatus } from '../lib/status'
import { fmtDateShort } from '../lib/dates'
const COLUMNS: BookingStatus[] = ['enquiry', 'reserved', 'confirmed', 'ended', 'cancelled']
type Booking = {
bookingId: string
eventName: string
startDate: Date
endDate: Date
status: string
clientName: string
isStammgruppe: boolean
depositPaid: boolean
hasDeposit: boolean
halfHouse: boolean
}
function BookingCard({ booking, locale }: { booking: Booking; locale: string }) {
useLocale()
const palette = BOOKING_STATUS[booking.status as BookingStatus]
return (
<Link
to="/admin/bookings/$bookingId"
params={{ bookingId: booking.bookingId }}
style={{ textDecoration: 'none', display: 'block' }}
>
<Paper
withBorder
radius="md"
p="sm"
style={{
borderLeft: `3px solid ${palette.dot}`,
cursor: 'pointer',
}}
>
<Stack gap={4}>
<Text size="sm" fw={600} lineClamp={2} c="var(--mantine-color-text)">
{asI18n(`${booking.eventName}`)}
</Text>
<Group gap={4}>
{booking.isStammgruppe && (
<Text size="xs" c="yellow.6" title={m.common__pages__admin_booking__client__regular_group()} style={{ cursor: 'default' }}>{asI18n('★')}</Text>
)}
<Text size="xs" c="dimmed" lineClamp={1}>{asI18n(`${booking.clientName}`)}</Text>
</Group>
<Text size="xs" c="dimmed">
{asI18n(`${fmtDateShort(booking.startDate, locale)} ${fmtDateShort(booking.endDate, locale)}`)}
</Text>
{booking.halfHouse && (
<Text size="xs" c="dimmed">{asI18n('½')}</Text>
)}
{booking.hasDeposit && (
<Text size="xs" c={booking.depositPaid ? 'green' : 'orange'} fw={500}>
{(booking.depositPaid ? m.common__pages__admin__bookings__deposit__paid : m.common__pages__admin__bookings__deposit__outstanding)()}
</Text>
)}
</Stack>
</Paper>
</Link>
)
}
function KanbanInner() {
useLocale()
usePageTitle(m.common__pages__kanban__title())
const { data, isLoading, error } = usePikkuQuery('getAdminBookings', { venueSlug: 'drawehn' })
if (isLoading) return <Container py="xl"><Text>{m.common__states__loading()}</Text></Container>
if (error) return <Container py="xl"><Text c="red">{m.common__states__error()}</Text></Container>
if (!data) return null
const byStatus = new Map<BookingStatus, Booking[]>()
for (const col of COLUMNS) byStatus.set(col, [])
for (const b of data.bookings) {
const col = b.status as BookingStatus
if (byStatus.has(col)) byStatus.get(col)!.push({ ...b, halfHouse: !!b.halfHouse } as unknown as Booking)
}
return (
<Box
style={{
overflowX: 'auto',
minHeight: 0,
paddingBottom: 24,
}}
px="xl"
pt="md"
>
<Group align="flex-start" wrap="nowrap" gap="md" style={{ minWidth: 'max-content' }}>
{COLUMNS.map((col) => {
const palette = BOOKING_STATUS[col]
const cards = byStatus.get(col) ?? []
return (
<Stack
key={col}
gap="sm"
style={{ width: 260, flexShrink: 0 }}
>
<Group gap="xs" align="center">
<Box
style={{
width: 8,
height: 8,
borderRadius: 4,
background: palette.dot,
flexShrink: 0,
}}
/>
<Text size="xs" fw={700} tt="uppercase" style={{ letterSpacing: '0.06em', color: palette.fg }}>
{bookingStatus[col]()}
</Text>
<Text size="xs" c="dimmed" ml="auto">
{cards.length}
</Text>
</Group>
<Stack gap="xs">
{cards.length === 0 ? (
<Paper withBorder radius="md" p="sm" style={{ borderStyle: 'dashed' }}>
<Text size="xs" c="dimmed" ta="center">{asI18n('—')}</Text>
</Paper>
) : (
cards.map((b) => (
<BookingCard key={b.bookingId} booking={b} locale={getLocale()} />
))
)}
</Stack>
</Stack>
)
})}
</Group>
</Box>
)
}
function KanbanPage() {
return (
<PublicShell>
<KanbanInner />
</PublicShell>
)
}
export const Route = createFileRoute('/kanban')({
component: KanbanPage,
})