277 lines
9.8 KiB
TypeScript
277 lines
9.8 KiB
TypeScript
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,
|
|
})
|