chore: perauset customer project
This commit is contained in:
180
apps/app/src/pages/_authenticated.tasks.templates.tsx
Normal file
180
apps/app/src/pages/_authenticated.tasks.templates.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user