chore: perauset customer project

This commit is contained in:
e2e
2026-07-11 08:54:43 +02:00
commit 49d05dfa0b
293 changed files with 28421 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
import { Table, Text, Card, Stack, Group, Box } from '@pikku/mantine/core'
import { useMediaQuery } from '@mantine/hooks'
import { type ReactNode } from 'react'
import { asI18n, type I18nNode } from '@pikku/react'
export interface Column<T> {
key: string
label: I18nNode
render: (row: T) => ReactNode
}
interface DataTableProps<T> {
data: T[]
columns: Column<T>[]
emptyMessage?: string
rowKey: (row: T) => string
}
export function DataTable<T>({
data,
columns,
emptyMessage = 'No data found.',
rowKey,
}: DataTableProps<T>) {
const isMobile = useMediaQuery('(max-width: 768px)')
if (data.length === 0) {
return (
<Card
padding="xl"
radius="md"
style={{
backgroundColor: '#ffffff',
boxShadow:
'0 2px 12px rgba(61, 43, 31, 0.08), 0 1px 4px rgba(61, 43, 31, 0.04)',
}}
>
<Text size="sm" c="dimmed" ta="center">
{asI18n(emptyMessage)}
</Text>
</Card>
)
}
if (isMobile) {
return (
<Stack gap="sm">
{data.map((row) => (
<Card
key={rowKey(row)}
padding="md"
radius="md"
style={{
backgroundColor: '#ffffff',
boxShadow:
'0 2px 12px rgba(61, 43, 31, 0.08), 0 1px 4px rgba(61, 43, 31, 0.04)',
}}
>
<Stack gap="xs">
{columns.map((col) => (
<Group key={col.key} justify="space-between" wrap="nowrap" gap="sm">
<Text size="xs" c="dimmed" fw={600} style={{ minWidth: 80 }}>
{col.label}
</Text>
<Box style={{ textAlign: 'right' }}>{col.render(row)}</Box>
</Group>
))}
</Stack>
</Card>
))}
</Stack>
)
}
return (
<Card
padding={0}
radius="md"
style={{
backgroundColor: '#ffffff',
boxShadow:
'0 2px 12px rgba(61, 43, 31, 0.08), 0 1px 4px rgba(61, 43, 31, 0.04)',
overflow: 'hidden',
}}
>
<Table
highlightOnHover
horizontalSpacing="lg"
verticalSpacing="sm"
styles={{
thead: {
backgroundColor: '#f8f6f2',
borderBottom: '2px solid rgba(61, 43, 31, 0.1)',
},
th: {
padding: '12px 16px',
fontSize: '11px',
fontWeight: 700,
letterSpacing: '0.05em',
textTransform: 'uppercase' as const,
color: '#8b7d6b',
},
td: {
padding: '14px 16px',
borderBottom: '1px solid rgba(61, 43, 31, 0.06)',
},
tr: {
transition: 'background-color 0.15s ease',
},
}}
>
<Table.Thead>
<Table.Tr>
{columns.map((col) => (
<Table.Th key={col.key}>{col.label}</Table.Th>
))}
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{data.map((row) => (
<Table.Tr key={rowKey(row)}>
{columns.map((col) => (
<Table.Td key={col.key}>{col.render(row)}</Table.Td>
))}
</Table.Tr>
))}
</Table.Tbody>
</Table>
</Card>
)
}

View File

@@ -0,0 +1,3 @@
export { PageHeader } from './page-header.js'
export { StatusBadge } from './status-badge.js'
export { DataTable, type Column } from './data-table.js'

View File

@@ -0,0 +1,32 @@
import { Title, Text, Group, Stack } from '@pikku/mantine/core'
import { type ReactNode } from 'react'
import type { I18nNode } from '@pikku/react'
export function PageHeader({
title,
description,
action,
}: {
title: I18nNode
description?: I18nNode
action?: ReactNode
}) {
return (
<Group justify="space-between" align="flex-start">
<Stack gap={4}>
<Title
order={2}
style={{ fontFamily: '"Cormorant", serif', color: '#3D2B1F' }}
>
{title}
</Title>
{description && (
<Text size="sm" c="dimmed">
{description}
</Text>
)}
</Stack>
{action}
</Group>
)
}

View File

@@ -0,0 +1,37 @@
import { Badge } from '@pikku/mantine/core'
import { asI18n } from '@pikku/react'
const STATUS_COLORS: Record<string, string> = {
submitted: 'yellow',
pending: 'yellow',
under_review: 'orange',
approved: 'blue',
confirmed: 'blue',
checked_in: 'green',
active: 'green',
open: 'green',
planned: 'blue',
checked_out: 'gray',
completed: 'gray',
full: 'orange',
closed: 'gray',
rejected: 'red',
declined: 'red',
cancelled: 'red',
blocked: 'pink',
waitlisted: 'orange',
}
function formatStatus(status: string): string {
return status.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase())
}
export function StatusBadge({ status }: { status: string }) {
const color = STATUS_COLORS[status] ?? 'gray'
return (
<Badge color={color} variant="light" size="sm">
{asI18n(formatStatus(status))}
</Badge>
)
}