import type { ActionArgs, LoaderArgs, MetaFunction } from '@remix-run/node'; import { json, redirect } from '@remix-run/node'; import { Form, useActionData, useCatch, useLoaderData, useNavigate } from '@remix-run/react'; import * as React from 'react'; import { Alert, Drawer, TextInput, Text, useMantineTheme, Group, Button, Textarea, Stack, Select, ColorSwatch, ActionIcon } from '@mantine/core'; import { AlertTriangle, Delete, Play, Save, Square, Trash } from 'react-feather'; import invariant from 'tiny-invariant'; import { deleteTimeEntry, getTimeEntry, updateTimeEntry } from '~/models/timeEntry.server'; import { requireUserId } from '~/session.server'; import { getProjects } from '~/models/project.server'; import { DatePicker, TimeInput } from '@mantine/dates'; export const meta: MetaFunction = () => { return { title: 'Edit Time Entry | WorkTimer', description: 'Edit a time entry. You must be logged in to do this.' }; }; export async function loader({ request, params }: LoaderArgs) { const userId = await requireUserId(request); invariant(params.timeEntryId, 'timeEntryId not found'); const timeEntry = await getTimeEntry({ userId, id: params.timeEntryId }); if (!timeEntry) { throw new Response('Not Found', { status: 404 }); } const projects = await getProjects({ userId }); return json({ timeEntry, projects }); } export async function action({ request, params }: ActionArgs) { const userId = await requireUserId(request); invariant(params.timeEntryId, 'timeEntryId not found'); const timeEntry = await getTimeEntry({ userId, id: params.timeEntryId }); if (!timeEntry) { throw new Response('Not Found', { status: 404 }); } if (request.method === 'DELETE') { await deleteTimeEntry({ userId, id: params.timeEntryId }); } else if (request.method === 'PATCH') { const formData = await request.formData(); const description = (formData.get('description') || undefined) as | string | undefined; const projectId = (formData.get('projectId') || undefined) as | string | undefined; let startTime = (formData.get('startTime') || undefined) as | string | undefined; let endTime = (formData.get('endTime') || undefined) as string | undefined; if ( startTime && typeof startTime === 'string' && !(startTime === 'now' || !Number.isNaN(Date.parse(startTime))) ) { return json( { errors: { projectId: null, description: null, startTime: 'startTime is invalid', endTime: null } }, { status: 422 } ); } if ( endTime && typeof endTime === 'string' && !(endTime === 'now' || !Number.isNaN(Date.parse(endTime))) ) { return json( { errors: { projectId: null, description: null, startTime: null, endTime: 'endTime is invalid' } }, { status: 422 } ); } let startDate = startTime ? new Date(startTime === 'now' ? Date.now() : startTime) : undefined; let endDate = endTime ? new Date(endTime === 'now' ? Date.now() : endTime) : undefined; if ( startDate && endDate && typeof startTime === 'string' && typeof endTime === 'string' && startDate > endDate ) { return json( { errors: { projectId: null, description: null, startTime: 'startTime must be before endTime', endTime: 'startTime must be before endTime' } }, { status: 422 } ); } await updateTimeEntry({ timeEntryId: params.timeEntryId, description, projectId, startTime: startDate, endTime: endDate, duration: endDate && startDate ? endDate.getTime() - startDate.getTime() : null }); } return redirect('/time-entries'); } interface ItemProps extends React.ComponentPropsWithoutRef<'div'> { id: string; label: string; color: string; } const SelectItem = React.forwardRef( ({ label, color, id, ...others }: ItemProps, ref) => (
{label}
) ); const LayoutWrapper = ({ children }: React.PropsWithChildren<{}>) => { const theme = useMantineTheme(); const navigate = useNavigate(); return ( { navigate('/time-entries'); }} > {children} ); }; export default function TimeEntryDetailsPage() { const actionData = useActionData(); const data = useLoaderData(); const theme = useMantineTheme(); const descriptionRef = React.useRef(null); const startDateRef = React.useRef(null); const endDateRef = React.useRef(null); const projectRef = React.useRef(null); const [start, setStart] = React.useState( new Date(data.timeEntry.startTime || Date.now()) ); const [end, setEnd] = React.useState( data.timeEntry.endTime ? new Date(data.timeEntry.endTime) : undefined ); React.useEffect(() => { if (actionData?.errors?.description) { descriptionRef.current?.focus(); } else if (actionData?.errors?.startTime) { startDateRef.current?.focus(); } else if (actionData?.errors?.endTime) { endDateRef.current?.focus(); } else if (actionData?.errors?.projectId) { projectRef.current?.focus(); } }, [actionData]); return (