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,180 @@
import { createFileRoute, Link } from '@tanstack/react-router'
import { Title, Text, Card, Group, Stack, Badge, Table, Anchor, Box } from '@pikku/mantine/core'
import { LayoutTemplate } from 'lucide-react'
import { asI18n } from '@pikku/react'
import { useLocale } from '@/i18n/config'
import { m } from '@/i18n/messages'
import { CreateTemplateForm } from '@/components/create-template-form'
export const Route = createFileRoute('/_authenticated/tasks/templates')({
component: TaskTemplatesPage,
})
const CATEGORY_COLORS: Record<string, string> = {
housekeeping: 'pink',
kitchen: 'yellow',
maintenance: 'orange',
operations: 'blue',
retreat: 'violet',
transport: 'cyan',
}
interface TaskTemplate {
templateId: string
title: string
description: string | null
category: string
isClaimable: boolean
defaultLocation: string | null
estimatedMinutes: number | null
recurrenceCron: string | null
}
function TaskTemplatesPage() {
useLocale()
// There is no list-templates RPC; new templates are created via the form below.
const templates: TaskTemplate[] = []
const error: string | null = null
return (
<Stack gap="lg">
<Group justify="space-between" align="flex-end">
<div>
<Anchor
component={Link}
to="/tasks"
size="sm"
c="dimmed"
style={{ textDecoration: 'none', marginBottom: 8, display: 'block' }}
>
{m.tasks_templates_back()}
</Anchor>
<Title order={2} style={{ fontFamily: '"Cormorant", serif', color: '#3D2B1F' }}>
{m.tasks_templates_title()}
</Title>
<Text size="sm" c="dimmed" mt={4}>
{m.tasks_templates_description()}
</Text>
</div>
</Group>
{error && (
<Card padding="md" radius="md" style={{ backgroundColor: '#fff3f3' }}>
<Text size="sm" c="red">
{error}
</Text>
</Card>
)}
<CreateTemplateForm />
{templates.length === 0 && !error ? (
<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)',
textAlign: 'center',
}}
>
<Stack align="center" gap="sm">
<LayoutTemplate size={40} color="#aaa" />
<Text size="sm" c="dimmed">
{m.tasks_templates_empty()}
</Text>
</Stack>
</Card>
) : (
<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',
}}
>
{/* Desktop table */}
<Box visibleFrom="sm">
<Table striped highlightOnHover>
<Table.Thead>
<Table.Tr>
<Table.Th>{m.tasks_templates_col_title()}</Table.Th>
<Table.Th>{m.tasks_templates_col_category()}</Table.Th>
<Table.Th>{m.tasks_templates_col_claimable()}</Table.Th>
<Table.Th>{m.tasks_templates_col_recurrence()}</Table.Th>
<Table.Th>{m.tasks_templates_col_duration()}</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{templates.map((t) => (
<Table.Tr key={t.templateId}>
<Table.Td>
<Text fw={500} size="sm">
{asI18n(t.title)}
</Text>
</Table.Td>
<Table.Td>
<Badge size="sm" variant="light" color={CATEGORY_COLORS[t.category] ?? 'gray'}>
{asI18n(t.category)}
</Badge>
</Table.Td>
<Table.Td>
<Badge size="sm" variant="light" color={t.isClaimable ? 'teal' : 'gray'}>
{t.isClaimable ? m.tasks_templates_yes() : m.tasks_templates_no()}
</Badge>
</Table.Td>
<Table.Td>
<Text size="sm" c="dimmed">
{asI18n(t.recurrenceCron ?? m.tasks_templates_one_time())}
</Text>
</Table.Td>
<Table.Td>
<Text size="sm" c="dimmed">
{asI18n(t.estimatedMinutes ? m.tasks_templates_minutes({ minutes: t.estimatedMinutes }) : '--')}
</Text>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</Box>
{/* Mobile cards */}
<Box hiddenFrom="sm" p="md">
<Stack gap="sm">
{templates.map((t) => (
<Card key={t.templateId} padding="sm" radius="sm" withBorder>
<Group justify="space-between" mb={4}>
<Text fw={600} size="sm">
{asI18n(t.title)}
</Text>
<Badge size="xs" variant="light" color={CATEGORY_COLORS[t.category] ?? 'gray'}>
{asI18n(t.category)}
</Badge>
</Group>
<Group gap="xs">
<Badge size="xs" variant="light" color={t.isClaimable ? 'teal' : 'gray'}>
{t.isClaimable ? m.tasks_templates_claimable() : m.tasks_templates_not_claimable()}
</Badge>
{t.recurrenceCron && (
<Badge size="xs" variant="light" color="grape">
{m.tasks_templates_recurring()}
</Badge>
)}
{t.estimatedMinutes && (
<Text size="xs" c="dimmed">
{m.tasks_templates_minutes({ minutes: t.estimatedMinutes })}
</Text>
)}
</Group>
</Card>
))}
</Stack>
</Box>
</Card>
)}
</Stack>
)
}