import { createFileRoute, Link } from '@tanstack/react-router' import { Title, Text, Card, Group, Stack, Badge, Button, SimpleGrid, Accordion, Anchor, Box, Drawer, Select, TextInput, Textarea, } from '@pikku/mantine/core' import { MapPin, Clock, Plus, Repeat, ShieldCheck } from 'lucide-react' import { useQueryClient } from '@tanstack/react-query' import { useDisclosure } from '@mantine/hooks' import { notifications } from '@mantine/notifications' import { useState } from 'react' import { asI18n } from '@pikku/react' import { usePikkuQuery, usePikkuMutation } from '@perauset/functions-sdk/pikku/api.gen' import { useLocale } from '@/i18n/config' import { m } from '@/i18n/messages' import { TaskActions } from '@/components/task-actions' export const Route = createFileRoute('/_authenticated/tasks/')({ component: TasksPage, }) const STATUS_CONFIG: Record = { open: { color: 'teal', order: 0 }, assigned: { color: 'blue', order: 1 }, in_progress: { color: 'orange', order: 2 }, blocked: { color: 'red', order: 3 }, planned: { color: 'gray', order: 4 }, completed: { color: 'green', order: 5 }, cancelled: { color: 'gray', order: 6 }, } const CATEGORY_COLORS: Record = { housekeeping: 'pink', kitchen: 'yellow', maintenance: 'orange', operations: 'blue', retreat: 'violet', transport: 'cyan', } const KANBAN_STATUSES = ['open', 'assigned', 'in_progress', 'blocked'] const CATEGORIES = [ { value: 'housekeeping', label: m.tasks_category_housekeeping() }, { value: 'kitchen', label: m.tasks_category_kitchen() }, { value: 'maintenance', label: m.tasks_category_maintenance() }, { value: 'operations', label: m.tasks_category_operations() }, { value: 'retreat', label: m.tasks_category_retreat() }, { value: 'transport', label: m.tasks_category_transport() }, ] const ROLE_OPTIONS = [ { value: 'admin', label: m.tasks_role_admin() }, { value: 'coordinator', label: m.tasks_role_coordinator() }, { value: 'facilitator', label: m.tasks_role_facilitator() }, { value: 'boat_coordinator', label: m.tasks_role_boat_coordinator() }, { value: 'kitchen_lead', label: m.tasks_role_kitchen_lead() }, { value: 'staff', label: m.tasks_role_staff() }, { value: 'volunteer', label: m.tasks_role_volunteer() }, { value: 'community_member', label: m.tasks_role_community_member() }, ] const STATUS_LABEL: Record string> = { open: () => m.tasks_status_open(), assigned: () => m.tasks_status_assigned(), in_progress: () => m.tasks_status_in_progress(), blocked: () => m.tasks_status_blocked(), planned: () => m.tasks_status_planned(), completed: () => m.tasks_status_completed(), cancelled: () => m.tasks_status_cancelled(), } interface TaskItem { taskId: string templateId: string | null title: string description: string | null category: string status: string location: string | null scheduledStartAt: string | Date | null scheduledEndAt: string | Date | null assignedRole: string | null createdByUserId: string } function formatTime(val: string | Date | null): string | null { if (!val) return null const d = typeof val === 'string' ? new Date(val) : val return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', }) } function statusLabel(status: string): string { return STATUS_LABEL[status]?.() ?? status } function TaskCard({ task, onAssignRole }: { task: TaskItem; onAssignRole: (taskId: string) => void }) { const cfg = STATUS_CONFIG[task.status] ?? { color: 'gray', order: 99 } return ( {asI18n(task.title)} {asI18n(statusLabel(task.status))} {asI18n(task.category)} {task.assignedRole && ( } > {asI18n(task.assignedRole.replace(/_/g, ' '))} )} {task.location && ( {asI18n(task.location)} )} {task.scheduledStartAt && ( {asI18n( `${formatTime(task.scheduledStartAt)}${task.scheduledEndAt ? ` - ${formatTime(task.scheduledEndAt)}` : ''}` )} )} {(task.status === 'open' || task.status === 'planned') && ( )} ) } function CreateTaskDrawer({ opened, onClose }: { opened: boolean; onClose: () => void }) { const queryClient = useQueryClient() const [title, setTitle] = useState('') const [description, setDescription] = useState('') const [category, setCategory] = useState(null) const [location, setLocation] = useState('') const [scheduledStartAt, setScheduledStartAt] = useState('') const [scheduledEndAt, setScheduledEndAt] = useState('') const [assignedRole, setAssignedRole] = useState(null) const assignToRole = usePikkuMutation('assignTaskToRole') const mutation = usePikkuMutation('createTaskInstance', { onSuccess: async (result) => { if (assignedRole && result.taskId) { await assignToRole.mutateAsync({ taskId: result.taskId, role: assignedRole }) } queryClient.invalidateQueries({ queryKey: ['listTasks'] }) notifications.show({ title: m.tasks_created_title(), message: m.tasks_created_message({ title }), color: 'teal', }) resetForm() onClose() }, onError: (err) => { notifications.show({ title: m.tasks_error_title(), message: err.message ?? m.tasks_error_generic(), color: 'red', }) }, }) const resetForm = () => { setTitle('') setDescription('') setCategory(null) setLocation('') setScheduledStartAt('') setScheduledEndAt('') setAssignedRole(null) } const handleSubmit = (e: React.FormEvent) => { e.preventDefault() if (!title.trim() || !category) { notifications.show({ title: m.tasks_validation_title(), message: m.tasks_validation_message(), color: 'orange', }) return } mutation.mutate({ title: title.trim(), category: category as 'housekeeping' | 'kitchen' | 'maintenance' | 'operations' | 'retreat' | 'transport', ...(description.trim() ? { description: description.trim() } : {}), ...(location.trim() ? { location: location.trim() } : {}), ...(scheduledStartAt ? { scheduledStartAt: new Date(scheduledStartAt).toISOString() } : {}), ...(scheduledEndAt ? { scheduledEndAt: new Date(scheduledEndAt).toISOString() } : {}), }) } return (
setTitle(e.currentTarget.value)} required />