2024-02-16 01:49:19 +00:00
|
|
|
'use client';
|
2024-02-15 02:30:10 +00:00
|
|
|
|
2024-02-16 01:49:19 +00:00
|
|
|
import { useEventListener, useOnClickOutside } from 'usehooks-ts';
|
|
|
|
import { useQueryClient } from '@tanstack/react-query';
|
|
|
|
import { useState, ElementRef, useRef } from 'react';
|
|
|
|
import { useParams } from 'next/navigation';
|
|
|
|
import { AlignLeft } from 'lucide-react';
|
|
|
|
import { toast } from 'sonner';
|
2024-02-15 02:30:10 +00:00
|
|
|
|
2024-02-16 01:49:19 +00:00
|
|
|
import { FormTextarea } from '@/components/form/form-textarea';
|
|
|
|
import { FormSubmit } from '@/components/form/form-submit';
|
|
|
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
|
|
import { updateCard } from '@/actions/update-card';
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
import { useAction } from '@/hooks/use-action';
|
2024-02-15 02:30:10 +00:00
|
|
|
|
2024-02-16 01:49:19 +00:00
|
|
|
import { CardWithList } from '@/types';
|
2024-02-15 02:30:10 +00:00
|
|
|
|
|
|
|
interface DescriptionProps {
|
|
|
|
data: CardWithList;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const Description = ({ data }: DescriptionProps) => {
|
|
|
|
const queryClient = useQueryClient();
|
|
|
|
const params = useParams();
|
|
|
|
|
|
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
|
|
|
2024-02-16 01:49:19 +00:00
|
|
|
const textareaRef = useRef<ElementRef<'textarea'>>(null);
|
|
|
|
const formRef = useRef<ElementRef<'form'>>(null);
|
2024-02-15 02:30:10 +00:00
|
|
|
|
|
|
|
const enaleEditing = () => {
|
|
|
|
setIsEditing(true);
|
|
|
|
setTimeout(() => {
|
|
|
|
textareaRef.current?.focus();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const disableEditing = () => {
|
|
|
|
setIsEditing(false);
|
|
|
|
};
|
|
|
|
|
|
|
|
const onKeyDown = (e: KeyboardEvent) => {
|
2024-02-16 01:49:19 +00:00
|
|
|
if (e.key === 'Escape') {
|
2024-02-15 02:30:10 +00:00
|
|
|
disableEditing();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-02-16 01:49:19 +00:00
|
|
|
useEventListener('keydown', onKeyDown);
|
2024-02-15 02:30:10 +00:00
|
|
|
useOnClickOutside(formRef, disableEditing);
|
|
|
|
|
|
|
|
const { execute, fieldErrors } = useAction(updateCard, {
|
|
|
|
onSuccess: (data) => {
|
2024-02-16 01:49:19 +00:00
|
|
|
queryClient.invalidateQueries({ queryKey: ['card', data.id] });
|
|
|
|
queryClient.invalidateQueries({ queryKey: ['card-logs', data.id] });
|
2024-02-15 02:30:10 +00:00
|
|
|
toast.success(`Card "${data.title}" updated`);
|
|
|
|
disableEditing();
|
|
|
|
},
|
|
|
|
onError: (error) => {
|
|
|
|
toast.error(error);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const onSubmit = (formData: FormData) => {
|
2024-02-16 01:49:19 +00:00
|
|
|
const description = formData.get('description') as string;
|
2024-02-15 02:30:10 +00:00
|
|
|
const boardId = params.boardId as string;
|
|
|
|
|
|
|
|
execute({
|
|
|
|
id: data.id,
|
|
|
|
description,
|
|
|
|
boardId,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
2024-02-16 01:49:19 +00:00
|
|
|
<div className='flex w-full items-start gap-x-3'>
|
|
|
|
<AlignLeft className='mt-0.5 h-5 w-5 text-neutral-700' />
|
|
|
|
<div className='w-full'>
|
|
|
|
<p className='mb-2 font-semibold text-neutral-700'>Description</p>
|
2024-02-15 02:30:10 +00:00
|
|
|
{isEditing ? (
|
2024-02-16 01:49:19 +00:00
|
|
|
<form ref={formRef} className='space-y-2' action={onSubmit}>
|
2024-02-15 02:30:10 +00:00
|
|
|
<FormTextarea
|
2024-02-16 01:49:19 +00:00
|
|
|
id='description'
|
2024-02-15 02:30:10 +00:00
|
|
|
ref={textareaRef}
|
2024-02-16 01:49:19 +00:00
|
|
|
className='mt-2 w-full'
|
|
|
|
placeholder='Add a more detailed description...'
|
2024-02-15 02:30:10 +00:00
|
|
|
defaultValue={data.description ?? undefined}
|
|
|
|
errors={fieldErrors}
|
|
|
|
/>
|
2024-02-16 01:49:19 +00:00
|
|
|
<div className='flex items-center gap-x-2'>
|
2024-02-15 02:30:10 +00:00
|
|
|
<FormSubmit>Save</FormSubmit>
|
|
|
|
<Button
|
2024-02-16 01:49:19 +00:00
|
|
|
type='button'
|
2024-02-15 02:30:10 +00:00
|
|
|
onClick={disableEditing}
|
2024-02-16 01:49:19 +00:00
|
|
|
size='sm'
|
|
|
|
variant='ghost'
|
2024-02-15 02:30:10 +00:00
|
|
|
>
|
|
|
|
Cancel
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
) : (
|
|
|
|
<div
|
|
|
|
onClick={enaleEditing}
|
2024-02-16 01:49:19 +00:00
|
|
|
role='button'
|
|
|
|
className='min-h-[78px] rounded-md bg-neutral-200 px-3.5 py-3 text-sm font-medium'
|
2024-02-15 02:30:10 +00:00
|
|
|
>
|
2024-02-16 01:49:19 +00:00
|
|
|
{data.description ?? 'Add a more detailed description...'}
|
2024-02-15 02:30:10 +00:00
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
Description.Skeleton = function DescriptionSkeleton() {
|
|
|
|
return (
|
2024-02-16 01:49:19 +00:00
|
|
|
<div className='flex w-full items-start gap-x-3'>
|
|
|
|
<Skeleton className='h-6 w-6 bg-neutral-200' />
|
|
|
|
<div className='w-full'>
|
|
|
|
<Skeleton className='mb-2 h-6 w-24 bg-neutral-200' />
|
|
|
|
<Skeleton className='h-[78px] w-full bg-neutral-200' />
|
2024-02-15 02:30:10 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|