206 lines
6.2 KiB
TypeScript
206 lines
6.2 KiB
TypeScript
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>
|
|
</>
|
|
)
|
|
}
|