chore: perauset customer project

This commit is contained in:
e2e
2026-07-11 08:34:30 +02:00
commit 0639909ec6
293 changed files with 28412 additions and 0 deletions

View File

@@ -0,0 +1,205 @@
import { useState } from 'react'
import {
Card,
Stack,
TextInput,
Textarea,
Select,
Group,
Button,
Switch,
NumberInput,
Collapse,
Title,
} from '@pikku/mantine/core'
import { useDisclosure } from '@mantine/hooks'
import { notifications } from '@mantine/notifications'
import { m } from '@/i18n/messages'
import { useQueryClient } from '@tanstack/react-query'
import { Plus } from 'lucide-react'
import { usePikkuMutation } from '@perauset/functions-sdk/pikku/api.gen'
type Category =
| 'housekeeping'
| 'kitchen'
| 'maintenance'
| 'operations'
| 'retreat'
| 'transport'
const CATEGORIES: { value: Category; label: string }[] = [
{ value: 'housekeeping', label: 'Housekeeping' },
{ value: 'kitchen', label: 'Kitchen' },
{ value: 'maintenance', label: 'Maintenance' },
{ value: 'operations', label: 'Operations' },
{ value: 'retreat', label: 'Retreat' },
{ value: 'transport', label: 'Transport' },
]
export function CreateTemplateForm() {
const queryClient = useQueryClient()
const [opened, { toggle }] = useDisclosure(false)
const [title, setTitle] = useState('')
const [description, setDescription] = useState('')
const [category, setCategory] = useState<Category | null>(null)
const [isClaimable, setIsClaimable] = useState(false)
const [defaultLocation, setDefaultLocation] = useState('')
const [estimatedMinutes, setEstimatedMinutes] = useState<number | string>('')
const [recurrenceCron, setRecurrenceCron] = useState('')
const resetForm = () => {
setTitle('')
setDescription('')
setCategory(null)
setIsClaimable(false)
setDefaultLocation('')
setEstimatedMinutes('')
setRecurrenceCron('')
}
const mutation = usePikkuMutation('createTaskTemplate', {
onSuccess: () => {
notifications.show({
title: m.templates_created(),
message: `"${title}" template has been created.`,
color: 'teal',
})
resetForm()
toggle()
queryClient.invalidateQueries({
predicate: (q) =>
typeof q.queryKey[0] === 'string' &&
(q.queryKey[0] as string).toLowerCase().includes('template'),
})
},
onError: (err: Error) => {
notifications.show({
title: m.common_error(),
message: err.message ?? 'Something went wrong',
color: 'red',
})
},
})
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (!title.trim() || !category) {
notifications.show({
title: m.common_validation(),
message: m.templates_title_category_required(),
color: 'orange',
})
return
}
const body = {
title: title.trim(),
category,
...(description.trim() ? { description: description.trim() } : {}),
...(isClaimable ? { isClaimable: true } : {}),
...(defaultLocation.trim() ? { defaultLocation: defaultLocation.trim() } : {}),
...(typeof estimatedMinutes === 'number' && estimatedMinutes > 0
? { estimatedMinutes }
: {}),
...(recurrenceCron.trim() ? { recurrenceCron: recurrenceCron.trim() } : {}),
}
mutation.mutate(body)
}
return (
<>
<Button variant="light" leftSection={<Plus size={16} />} onClick={toggle}>
{opened ? m.templates_hide_form() : m.templates_create()}
</Button>
<Collapse expanded={opened}>
<Card
padding="lg"
radius="md"
style={{
backgroundColor: '#ffffff',
boxShadow:
'0 2px 12px rgba(61, 43, 31, 0.08), 0 1px 4px rgba(61, 43, 31, 0.04)',
maxWidth: 640,
}}
>
<Title
order={4}
mb="md"
style={{ fontFamily: '"Cormorant", serif', color: '#3D2B1F' }}
>
{m.templates_new()}
</Title>
<form onSubmit={handleSubmit}>
<Stack gap="md">
<TextInput
label={m.templates_field_title()}
placeholder={m.templates_field_title_placeholder()}
value={title}
onChange={(e) => setTitle(e.currentTarget.value)}
required
/>
<Textarea
label={m.templates_field_description()}
placeholder={m.templates_field_description_placeholder()}
value={description}
onChange={(e) => setDescription(e.currentTarget.value)}
minRows={2}
/>
<Select
label={m.templates_field_category()}
placeholder={m.templates_field_category_placeholder()}
data={CATEGORIES}
value={category}
onChange={(v) => setCategory(v as Category | null)}
required
/>
<Switch
label={m.templates_field_claimable()}
checked={isClaimable}
onChange={(e) => setIsClaimable(e.currentTarget.checked)}
/>
<TextInput
label={m.templates_field_default_location()}
placeholder={m.templates_field_default_location_placeholder()}
value={defaultLocation}
onChange={(e) => setDefaultLocation(e.currentTarget.value)}
/>
<NumberInput
label={m.templates_field_estimated_minutes()}
placeholder={m.templates_field_estimated_minutes_placeholder()}
min={1}
value={estimatedMinutes}
onChange={setEstimatedMinutes}
/>
<TextInput
label={m.templates_field_recurrence()}
placeholder={m.templates_field_recurrence_placeholder()}
description={m.templates_recurrence_hint()}
value={recurrenceCron}
onChange={(e) => setRecurrenceCron(e.currentTarget.value)}
/>
<Group justify="flex-end" mt="sm">
<Button variant="default" onClick={toggle}>
{m.common_cancel()}
</Button>
<Button type="submit" loading={mutation.isPending}>
{m.templates_create()}
</Button>
</Group>
</Stack>
</form>
</Card>
</Collapse>
</>
)
}