import './HerdView.scss' import BackButton from '@/components/button/BackButton' import Button from '@/components/button/Button' import ButtonSet from '@/components/ButtonSet' import Chip from '@/components/Chip' import CreateButton from '@/components/button/CreateButton' import DeleteButton from '@/components/button/DeleteButton' import { DndContext } from '@dnd-kit/core' import type { DragEndEvent } from '@dnd-kit/core' import FormGroup from '@/components/form/FormGroup' import FormInput from '@/components/form/FormInput' import LoadingIndicator from '@/components/LoadingIndicator' import Main from '@/components/Main' import Notice from '@/components/Notice' import Pagination from '@/components/Pagination' import Placeholder from '@/components/Placeholder' import ResetButton from '@/components/button/ResetButton' import Row from '@/components/Row' import SaveButton from '@/components/button/SaveButton' import SearchForm from '@/components/SearchForm' import SortableRow from '@/components/SortableRow' import type { SubmitHandler } from 'react-hook-form' import api from '@/api' import { useForm } from 'react-hook-form' import { CheckCircleIcon, CloudIcon, XCircleIcon, XMarkIcon } from '@heroicons/react/20/solid' import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable' import { useCallback, useEffect, useState } from 'react' import { useConnection, useRouteSearch, useSession } from '@/hooks' import { useNavigate, useParams } from 'react-router-dom' interface HerdUpdateFormData extends Pick {} interface TaskCreateFormData extends Pick {} interface TaskUpdateFormData extends Pick {} function useHerdUpdateForm() { const form = useForm({ mode: 'onBlur' }) const inputs = { name: form.register('name', { validate: value => { if (value.length < 1) return 'Required' }}), } return { ...form, inputs } } function useTaskCreateForm() { const form = useForm({ mode: 'onBlur' }) const inputs = { description: form.register('description', { validate: value => { if (value.length < 1) return 'Required' }}), } return { ...form, inputs } } function useTaskUpdateForm() { const form = useForm({ mode: 'onBlur' }) const inputs = { description: form.register('description', { validate: value => { if (value.length < 1) return 'Required' }}), } return { ...form, inputs } } export default function HerdView() { const { account } = useSession() const createTaskForm = useTaskCreateForm() const { id } = useParams() const navigate = useNavigate() const { options } = useConnection() const updateHerdForm = useHerdUpdateForm() const updateTaskForm = useTaskUpdateForm() const { limit, page, searchParams, setPage } = useRouteSearch() const [data, setData] = useState() const [taskData, setTaskData] = useState>() const [busy, setBusy] = useState(false) const [editing, setEditing] = useState() const [error, setError] = useState() const [loading, setLoading] = useState(false) const disableSorting = Boolean(searchParams.search) async function createTask(data: TaskCreateFormData) { if (busy) return try { setBusy(true) setError(undefined) await api.createTask(options, { task: { _herd: id || '', _account: account?._id || '', description: data.description, }, }) createTaskForm.reset({ description: '' }) if (taskData && taskData.results.length >= limit) { // New task will be on a new page; change page to display it setPage(page + 1) } else { // Reload current page const taskRes = await api.searchTasks(options, id, searchParams) setTaskData(taskRes) } } catch (err) { setError(err as Error) } finally { setBusy(false) } } async function deleteHerd() { if (busy || !data) return try { setBusy(true) setError(undefined) await api.deleteHerd(options, data.herd._id) navigate('/') } catch (err) { setError(err as Error) } finally { setBusy(false) } } async function deleteTask(task: api.WithId) { if (busy) return try { setBusy(true) setError(undefined) await api.deleteTask(options, task._id) // Reload current page const taskRes = await api.searchTasks(options, id, searchParams) setTaskData(taskRes) } catch (err) { setError(err as Error) } finally { setBusy(false) } } async function moveTask({ active, over }: DragEndEvent) { if (busy || !taskData || !over || active.id === over.id) return const activeIdx = taskData.results.findIndex(({ task }) => task._id === active.id) const overIdx = taskData.results.findIndex(({ task }) => task._id === over.id) if (activeIdx < 0 || overIdx < 0) return const target = taskData.results[overIdx] try { setBusy(true) setError(undefined) const update = await api.moveTask(options, active.id.toString(), target.task.position) // Hot reorder tasks const results = activeIdx < overIdx // Task moved down ? [ ...taskData.results.slice(0, overIdx + 1).filter(({ task }) => task._id !== update.task._id), update, ...taskData.results.slice(overIdx +1), ] // Task moved up : [ ...taskData.results.slice(0, overIdx), update, ...taskData.results.slice(overIdx).filter(({ task }) => task._id !== update.task._id), ] setTaskData({ ...taskData, results }) } catch (err) { setError(err as Error) } finally { setBusy(false) } } async function toggleTaskDone(task: api.WithId) { try { setBusy(true) setError(undefined) const update = await api.toggleTaskDone(options, task._id) const inPageTask = taskData?.results.find(({ task }) => task._id === update.task._id) if (inPageTask) inPageTask.task.done = update.task.done } catch (err) { setError(err as Error) } finally { setBusy(false) } } const reload = useCallback(async () => { if (id) { setError(undefined) setLoading(true) try { const res = await api.getHerd(options, id) setData(res) const taskRes = await api.searchTasks(options, id, searchParams) setTaskData(taskRes) } catch (err) { setError(err as Error) } finally { setLoading(false) } } }, [id, options, searchParams]) function setTaskToEdit(task?: api.WithId) { if (task) { setEditing(task._id) updateTaskForm.reset({ description: task.description }) } else { setEditing(undefined) } } async function updateHerd(data: HerdUpdateFormData) { if (busy) return try { setBusy(true) setError(undefined) const res = await api.updateHerd(options, id as string, { herd: data, }) setData(res) } catch (err) { setError(err as Error) } finally { setBusy(false) } } function updateTask(task: api.WithId): SubmitHandler { return async function(data) { if (busy) return try { setBusy(true) setError(undefined) const update = await api.updateTask(options, task._id, { task: data }) const inPageTask = taskData?.results.find(({ task }) => task._id === update.task._id) if (inPageTask) { inPageTask.task.description = update.task.description } setEditing(undefined) } catch (err) { setError(err as Error) } finally { setBusy(false) } } } useEffect(() => { reload() }, [reload]) if (loading) return (
{id ?

Loading Herd...

:

Create Herd

}
) if (error) return (
{id ?

Loading Herd...

:

Create Herd

}
) return data && (

{data.herd.name}

navigate('/')} />
{taskData && ( <> {taskData.metadata.totalCount > 0 ? ( task._id)} strategy={verticalListSortingStrategy} > {taskData.results.map(({ task }, i) => (
{i + 1 + ((page - 1) * limit)}
{editing === task._id ? (
) : ( setTaskToEdit(task)}>{task.description} )}
{task.done ? ( ) : ( )} deleteTask(task)} />
))}
) : ( No tasks! )}
)}
) }