chore: seminarhof customer project

This commit is contained in:
e2e
2026-07-10 23:39:57 +02:00
commit 00c0dbd5ea
188 changed files with 20779 additions and 0 deletions

View File

@@ -0,0 +1,276 @@
import { Navigate, createFileRoute } from '@tanstack/react-router'
import { m } from '@/i18n/messages'
import { useLocale, getLocale } from '@/i18n/config'
import { asI18n } from '@pikku/react'
import {
Badge,
Box,
Button,
Container,
Paper,
Progress,
Stack,
Table,
Text,
Title,
} from '@pikku/mantine/core'
import { BrandHeader } from '../components/Brand'
import { RequireAuth, useAuth } from '../auth'
import { usePageTitle } from '../components/AppLayout'
import { fmtDate } from '../lib/dates'
import { usePikkuQuery } from '@project/functions-sdk/pikku/api.gen'
function MarketingPage({ hero, sections }: { hero: React.ReactNode; sections: React.ReactNode[] }) {
return (
<Box>
{hero}
<Container size="md" py="xl">
<Stack gap="lg">{sections}</Stack>
</Container>
</Box>
)
}
function IndexPage() {
useLocale()
return (
<MarketingPage
hero={(
<Box bg="var(--brand-plum)" py="xl" c="white">
<Container size="md">
<BrandHeader variant="white" />
</Container>
</Box>
)}
sections={[
<Paper key="launchpad" withBorder radius="md" p="lg">
<Stack gap="md">
<Text size="xs" c="dimmed" fw={600} tt="uppercase">
{m.common__pages__launchpad__title()}
</Text>
<Paper withBorder radius="md" p="md">
<Stack gap="xs">
<Title order={4}>{m.common__pages__launchpad__client__title()}</Title>
<Text size="sm" c="dimmed">
{m.common__pages__launchpad__client__body()}
</Text>
<Box>
<Button component="a" href="/admin/bookings" size="sm" variant="light">
{m.common__pages__launchpad__client__cta()}
</Button>
</Box>
</Stack>
</Paper>
<Paper withBorder radius="md" p="md">
<Stack gap="xs">
<Title order={4}>{m.common__pages__launchpad__availability__title()}</Title>
<Text size="sm" c="dimmed">
{m.common__pages__launchpad__availability__body()}
</Text>
<Box>
<Button component="a" href="/availability" size="sm" variant="light">
{m.common__pages__launchpad__availability__cta()}
</Button>
</Box>
</Stack>
</Paper>
<Paper withBorder radius="md" p="md">
<Stack gap="xs">
<Title order={4}>{m.common__pages__launchpad__admin__title()}</Title>
<Text size="sm" c="dimmed">
{m.common__pages__launchpad__admin__body()}
</Text>
<Box>
<Button component="a" href="/admin/bookings" size="sm" variant="light">
{m.common__pages__launchpad__admin__cta()}
</Button>
</Box>
</Stack>
</Paper>
<Paper withBorder radius="md" p="md">
<Stack gap="xs">
<Title order={4}>{m.common__pages__launchpad__events__title()}</Title>
<Text size="sm" c="dimmed">
{m.common__pages__launchpad__events__body()}
</Text>
<Box>
<Button component="a" href="/events" size="sm" variant="light">
{m.common__pages__launchpad__events__cta()}
</Button>
</Box>
</Stack>
</Paper>
<Paper withBorder radius="md" p="md">
<Stack gap="xs">
<Title order={4}>{m.common__pages__launchpad__enquiry__title()}</Title>
<Text size="sm" c="dimmed">
{m.common__pages__launchpad__enquiry__body()}
</Text>
<Box>
<Button component="a" href="/enquiry" size="sm" variant="light">
{m.common__pages__launchpad__enquiry__cta()}
</Button>
</Box>
</Stack>
</Paper>
</Stack>
</Paper>,
]}
/>
)
}
function ClientOverview() {
useLocale()
usePageTitle(m.common__pages__client__title())
const { data, isLoading, error } = usePikkuQuery('getClientOverview', null)
if (isLoading && !data) {
return <Container py="xl"><Text>{m.common__states__loading()}</Text></Container>
}
if (error && !data) {
return <Container py="xl"><Text c="red">{m.common__states__error()}</Text></Container>
}
if (!data) return null
return (
<Container size="lg" py="md">
<Stack gap="lg">
<Paper withBorder radius="md" p="lg">
<Stack gap="xs">
<Text size="xs" c="dimmed" fw={600} tt="uppercase">
{m.common__pages__client__title()}
</Text>
<Title order={2}>{m.common__brand__name()}</Title>
{data.clients.length > 0 && (
<Text c="dimmed">
{asI18n(data.clients.map((client) => client.name).join(', '))}
</Text>
)}
</Stack>
</Paper>
<Paper withBorder radius="md" p="lg">
<Stack gap="md">
<Title order={3}>{m.common__pages__client__upcoming()}</Title>
{data.upcomingBookings.length === 0 ? (
<Text c="dimmed">{m.common__pages__client__no_upcoming()}</Text>
) : (
data.upcomingBookings.map((booking) => (
<Paper key={booking.bookingId} withBorder radius="md" p="md">
<Stack gap="xs">
<Stack gap={4}>
<Text fw={600}>{asI18n(`${booking.eventName}`)}</Text>
<Text size="sm" c="dimmed">
{asI18n(`${fmtDate(booking.startDate, getLocale())} - ${fmtDate(booking.endDate, getLocale())}`)}
</Text>
</Stack>
{booking.isCurrentEvent && (
<Box>
<Badge color="plum" variant="light">
{m.common__pages__client__current_badge()}
</Badge>
</Box>
)}
<Text size="sm">
{booking.expectedPersons
? m.common__pages__client__participants_count({
count: booking.participantCount,
target: booking.expectedPersons,
})
: m.common__pages__client__participants_count_unknown({
count: booking.participantCount,
})}
</Text>
<Stack gap={4}>
<Text size="sm" c="dimmed">
{m.common__pages__client__completeness()}
</Text>
<Progress
color="plum"
radius="xl"
size="lg"
value={booking.completenessPercent}
/>
</Stack>
</Stack>
</Paper>
))
)}
</Stack>
</Paper>
<Paper withBorder radius="md" p="lg">
<Stack gap="md">
<Title order={3}>{m.common__pages__client__recent_invoices()}</Title>
{data.recentInvoices.length === 0 ? (
<Text c="dimmed">{m.common__pages__client__no_invoices()}</Text>
) : (
<Table>
<Table.Thead>
<Table.Tr>
<Table.Th>{m.common__pages__client__invoice_cols__booking()}</Table.Th>
<Table.Th>{m.common__pages__client__invoice_cols__number()}</Table.Th>
<Table.Th>{m.common__pages__client__invoice_cols__kind()}</Table.Th>
<Table.Th>{m.common__pages__client__invoice_cols__amount()}</Table.Th>
<Table.Th>{m.common__pages__client__invoice_cols__status()}</Table.Th>
<Table.Th>{m.common__pages__client__invoice_cols__issued()}</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{data.recentInvoices.map((invoice) => (
<Table.Tr key={invoice.invoiceId}>
<Table.Td>{invoice.bookingName}</Table.Td>
<Table.Td>
{invoice.pdfUrl ? (
<Button
component="a"
href={invoice.pdfUrl}
target="_blank"
rel="noreferrer"
download={`${invoice.invoiceNumber}.pdf`}
variant="subtle"
px={0}
>
{asI18n(`${invoice.invoiceNumber}`)}
</Button>
) : (
invoice.invoiceNumber
)}
</Table.Td>
<Table.Td>{invoice.kind}</Table.Td>
<Table.Td>{(invoice.amountCents / 100).toFixed(2)} </Table.Td>
<Table.Td>{invoice.status}</Table.Td>
<Table.Td>{fmtDate(invoice.issuedOn, getLocale())}</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
)}
</Stack>
</Paper>
</Stack>
</Container>
)
}
function Dashboard() {
const { role } = useAuth()
if (role === 'admin' || role === 'owner') return <Navigate to="/admin/bookings" />
if (role === 'client') return <ClientOverview />
return <IndexPage />
}
function IndexRoute() {
return (
<RequireAuth>
<Dashboard />
</RequireAuth>
)
}
export const Route = createFileRoute('/')({
component: IndexRoute,
})