Skip to content

Commit

Permalink
refactor: 섹션 및 태스크 분리
Browse files Browse the repository at this point in the history
  • Loading branch information
PMtHk committed Dec 3, 2024
1 parent ed3a347 commit b44a5d7
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 130 deletions.
156 changes: 43 additions & 113 deletions apps/client/src/features/project/board/components/KanbanBoard.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
import { DragEvent } from 'react';
import { Link } from '@tanstack/react-router';
import { motion, AnimatePresence } from 'framer-motion';
import { PanelLeftOpen } from 'lucide-react';
import { Button } from '@/components/ui/button.tsx';
import { Card, CardContent, CardFooter, CardHeader } from '@/components/ui/card.tsx';
import { cn } from '@/lib/utils.ts';
import { Badge } from '@/components/ui/badge.tsx';
import { AnimatePresence } from 'framer-motion';
import { useToast } from '@/lib/useToast.tsx';
import { useBoardMutations } from '@/features/project/board/useBoardMutations.ts';
import { AssigneeAvatars } from '@/features/project/board/components/AssigneeAvatars.tsx';
import { calculatePosition, findDiff, findTask } from '@/features/project/board/utils.ts';
import { TaskTextarea } from '@/features/project/board/components/TaskTextarea.tsx';
import { useBoardStore } from '@/features/project/board/useBoardStore.ts';
import { SubtaskProgress } from '@/features/project/board/components/SubtaskProgress.tsx';
import { useDragAndDrop } from '@/features/project/board/useDragAndDrop.ts';
import { BoardSection } from '@/features/project/board/components/KanbanSection.tsx';
import { TaskCard } from '@/features/project/board/components/KanbanTask.tsx';

interface KanbanBoardProps {
projectId: number;
Expand All @@ -24,7 +15,11 @@ export function KanbanBoard({ projectId }: KanbanBoardProps) {
const { sections, updateTaskPosition, updateTaskTitle, restoreState } = useBoardStore();

const toast = useToast();
const mutations = useBoardMutations(projectId);

const {
updateTitle: { mutate: updateTitleMutate },
updatePosition: { mutate: updatePositionMutate },
} = useBoardMutations(projectId);

const onDrop = (sectionId: number, taskId: number) => {
const targetSection = sections.find((section) => section.id === sectionId);
Expand All @@ -39,7 +34,7 @@ export function KanbanBoard({ projectId }: KanbanBoardProps) {

updateTaskPosition(sectionId, taskId, position);

mutations.updatePosition.mutate(
updatePositionMutate(
{
event: 'UPDATE_POSITION',
sectionId,
Expand Down Expand Up @@ -70,43 +65,36 @@ export function KanbanBoard({ projectId }: KanbanBoardProps) {

const diff = findDiff(task.title, newTitle);

const deletePayload = {
event: 'DELETE_TITLE' as const,
taskId,
title: {
position: diff.position,
content: diff.originalContent,
length: diff.originalContent.length,
},
};

const insertPayload = {
event: 'INSERT_TITLE' as const,
taskId,
title: {
position: diff.position,
content: diff.content,
length: diff.content.length,
},
};

if (diff.originalContent.length > 0) {
mutations.updateTitle.mutate(
{
event: 'DELETE_TITLE',
taskId,
title: {
position: diff.position,
content: diff.originalContent,
length: diff.originalContent.length,
},
},
{
onSuccess: () => {
if (diff.content.length > 0) {
mutations.updateTitle.mutate({
event: 'INSERT_TITLE',
taskId,
title: {
position: diff.position,
content: diff.content,
length: diff.content.length,
},
});
}
},
}
);
} else if (diff.content.length > 0) {
mutations.updateTitle.mutate({
event: 'INSERT_TITLE',
taskId,
title: {
position: diff.position,
content: diff.content,
length: diff.content.length,
updateTitleMutate(deletePayload, {
onSuccess: () => {
if (diff.content.length > 0) {
updateTitleMutate(insertPayload);
}
},
});
} else if (diff.content.length > 0) {
updateTitleMutate(insertPayload);
}

updateTaskTitle(taskId, newTitle);
Expand All @@ -124,74 +112,16 @@ export function KanbanBoard({ projectId }: KanbanBoardProps) {
onDragOver={(e) => handleDragOver(e, section.id)}
>
{section.tasks.map((task) => (
<motion.div
<TaskCard
key={task.id}
layout
layoutId={task.id.toString()}
draggable
initial={{ opacity: 1, zIndex: 1 }}
animate={{
zIndex: task.id === belowTaskId ? 50 : 1,
scale: task.id === belowTaskId ? 1.02 : 1,
}}
transition={{
layout: { duration: 0.3 },
scale: { duration: 0.2 },
}}
style={{ position: 'relative' }}
onDragStart={(e) => handleDragStart(e as unknown as DragEvent, section.id, task.id)}
onDrop={(e) => handleDrop(e, section.id)}
onDragOver={(e) => {
e.preventDefault();
e.stopPropagation();
handleDragOver(e, section.id, task.id);
}}
task={task}
isBelow={task.id === belowTaskId}
onDragStart={(e) => handleDragStart(e, section.id, task.id)}
onDragOver={(e) => handleDragOver(e, section.id, task.id)}
onDragLeave={handleDragLeave}
>
<Card
className={cn(
'w-56 border bg-white transition-all duration-300 md:w-80',
task.id === belowTaskId && 'border-2 border-blue-400',
'hover:shadow-md'
)}
>
<CardHeader className="flex flex-row items-start gap-2 space-y-0">
<TaskTextarea
taskId={task.id}
initialTitle={task.title}
onTitleChange={handleTitleChange}
/>
<Button
variant="ghost"
type="button"
asChild
className="hover:text-primary px-2 hover:bg-transparent"
>
<Link to={`/${projectId}/board/${task.id}`} className="p-0">
<PanelLeftOpen className="h-6 w-6" />
</Link>
</Button>
</CardHeader>
<CardContent className="flex items-end justify-between">
<div className="flex flex-wrap gap-1">
{task.labels.map((label) => (
<Badge key={label.id} style={{ backgroundColor: label.color }}>
{label.name}
</Badge>
))}
</div>
<AssigneeAvatars assignees={task.assignees} />
</CardContent>
{task.subtasks.total > 0 && (
<CardFooter className="flex items-center justify-between space-y-0">
<SubtaskProgress
total={task.subtasks.total}
completed={task.subtasks.completed}
/>
</CardFooter>
)}
</Card>
</motion.div>
onDrop={(e) => handleDrop(e, section.id)}
onTitleChange={handleTitleChange}
/>
))}
</BoardSection>
))}
Expand Down
28 changes: 11 additions & 17 deletions apps/client/src/features/project/board/components/KanbanSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,7 @@ export function BoardSection({
<SectionCount>{section.tasks.length}</SectionCount>
</div>
<SectionDropdownMenu>
<Button
type="button"
variant="ghost"
className="hover:text-primary w-full border-none px-0 text-black hover:bg-white"
onClick={handleCreateTask}
>
<PlusIcon />
Add Task
</Button>
<AddTaskButton onClick={handleCreateTask} />
</SectionDropdownMenu>
</SectionHeader>

Expand All @@ -112,14 +104,7 @@ export function BoardSection({
{children}
</SectionContent>
<SectionFooter className="w-full">
<Button
variant="ghost"
className="w-full border-none px-0 text-black"
onClick={handleCreateTask}
>
<PlusIcon />
Add Task
</Button>
<AddTaskButton onClick={handleCreateTask} />
</SectionFooter>
</Section>
);
Expand Down Expand Up @@ -147,3 +132,12 @@ function SectionDropdownMenu({ children }: { children: ReactNode }) {
</DropdownMenu>
);
}

function AddTaskButton({ onClick }: { onClick: () => void }) {
return (
<Button variant="ghost" className="w-full border-none px-0 text-black" onClick={onClick}>
<PlusIcon />
Add Task
</Button>
);
}
100 changes: 100 additions & 0 deletions apps/client/src/features/project/board/components/KanbanTask.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { DragEvent } from 'react';
import { motion } from 'framer-motion';
import { Link, useLoaderData } from '@tanstack/react-router';
import { PanelLeftOpen } from 'lucide-react';
import { Card, CardContent, CardFooter, CardHeader } from '@/components/ui/card.tsx';
import { Task } from '@/features/project/board/types.ts';
import { cn } from '@/lib/utils.ts';
import { TaskTextarea } from '@/features/project/board/components/TaskTextarea.tsx';
import { Button } from '@/components/ui/button.tsx';
import { Badge } from '@/components/ui/badge.tsx';
import { AssigneeAvatars } from '@/features/project/board/components/AssigneeAvatars.tsx';
import { SubtaskProgress } from '@/features/project/board/components/SubtaskProgress.tsx';

interface TaskCardProps {
task: Task;
isBelow: boolean;
onDragStart: (e: DragEvent) => void;
onDragOver: (e: DragEvent) => void;
onDragLeave: () => void;
onDrop: (e: DragEvent) => void;
onTitleChange: (id: number, title: string) => void;
}

export function TaskCard({
task,
isBelow,
onDragStart,
onDragOver,
onDragLeave,
onDrop,
onTitleChange,
}: TaskCardProps) {
const { projectId } = useLoaderData({
from: '/_auth/$project/board',
});

return (
<motion.div
key={task.id}
layout
layoutId={task.id.toString()}
draggable
initial={{ opacity: 1, zIndex: 1 }}
animate={{
zIndex: isBelow ? 50 : 1,
scale: isBelow ? 1.02 : 1,
}}
transition={{
layout: { duration: 0.2 },
scale: { duration: 0.2 },
}}
style={{ position: 'relative' }}
onDragStart={(e) => onDragStart(e as unknown as DragEvent)}
onDrop={(e) => onDrop(e)}
onDragOver={(e) => {
e.preventDefault();
e.stopPropagation();
onDragOver(e);
}}
onDragLeave={onDragLeave}
>
<Card
className={cn(
'w-56 border bg-white transition-all duration-300 md:w-80',
isBelow && 'border-2 border-blue-400',
'hover:shadow-md'
)}
>
<CardHeader className="flex flex-row items-start gap-2 space-y-0">
<TaskTextarea taskId={task.id} initialTitle={task.title} onTitleChange={onTitleChange} />
<Button
variant="ghost"
type="button"
asChild
className="hover:text-primary px-2 hover:bg-transparent"
>
<Link to={`/${projectId}/board/${task.id}`} className="p-0">
<PanelLeftOpen className="h-6 w-6" />
</Link>
</Button>
</CardHeader>
<CardContent className="flex items-end justify-between">
<div className="flex flex-wrap gap-1">
{task.labels.map((label) => (
<Badge key={label.id} style={{ backgroundColor: label.color }}>
{label.name}
</Badge>
))}
</div>
<AssigneeAvatars assignees={task.assignees} />
</CardContent>
{task.subtasks.total > 0 && (
<CardFooter className="flex items-center justify-between space-y-0">
<SubtaskProgress total={task.subtasks.total} completed={task.subtasks.completed} />
</CardFooter>
)}
</Card>
</motion.div>
);
}

0 comments on commit b44a5d7

Please sign in to comment.