Skip to content

Commit

Permalink
feat: 태스크 생성 기능 연결
Browse files Browse the repository at this point in the history
  • Loading branch information
PMtHk committed Nov 18, 2024
1 parent bf6b69c commit 66bde21
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 21 deletions.
121 changes: 103 additions & 18 deletions apps/client/src/components/Kanban.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { ReactNode } from 'react';
import { Dispatch, ReactNode, SetStateAction } from 'react';
import { useParams } from '@tanstack/react-router';
import { HamburgerMenuIcon, PlusIcon, TrashIcon } from '@radix-ui/react-icons';
import { LexoRank } from 'lexorank';
import axios from 'axios';
import {
Section,
SectionContent,
Expand All @@ -15,12 +18,76 @@ import {
DropdownMenuContent,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu.tsx';
import { useAuth } from '@/contexts/authContext.tsx';

interface KanbanProps {
sections: TSection[];
setSections: Dispatch<SetStateAction<TSection[]>>;
}

export default function Kanban({ sections }: KanbanProps) {
export default function Kanban({ sections, setSections }: KanbanProps) {
const { project: projectId } = useParams({ from: '/_auth/$project/board' });
const auth = useAuth();

const handleCreateTask = async (sectionId: number) => {
const section = sections.find((section) => section.id === sectionId);
const lastTask = section?.tasks[section.tasks.length - 1];
const afterLastTaskPosition = lastTask
? LexoRank.parse(lastTask.position).genNext()
: LexoRank.middle();

const prevSections = [...sections];

try {
setSections((sections) =>
sections.map((section) => {
if (section.id === sectionId) {
return {
...section,
tasks: [
...section.tasks,
{
id: -1, // 임시 ID 부여
title: '',
description: '',
position: afterLastTaskPosition.toString(),
},
],
};
}
return section;
})
);

const response = await axios.post(
`/api/project/${projectId}/update`,
{
event: 'CREATE_TASK',
sectionId,
position: afterLastTaskPosition.toString(),
},
{
headers: { Authorization: `Bearer ${auth.accessToken}` },
}
);

if (response.status === 200) {
const { id } = response.data.result;
setSections((sections) =>
sections.map((section) =>
section.id === sectionId
? {
...section,
tasks: section.tasks.map((task) => (task.id === -1 ? { ...task, id } : task)),
}
: section
)
);
}
} catch {
setSections(prevSections);
}
};
return (
<SectionWrapper>
{sections.map((section: TSection) => (
Expand All @@ -30,16 +97,45 @@ export default function Kanban({ sections }: KanbanProps) {
<SectionTitle className="text-xl">{section.name}</SectionTitle>
<SectionCounter>{section.tasks.length || 0}</SectionCounter>
</div>
<SectionMenu />
<SectionMenu>
<Button
type="button"
variant="ghost"
className="w-full border-none px-0 text-black"
onClick={() => handleCreateTask(section.id)}
>
<PlusIcon />
태스크 추가
</Button>
<Button
type="button"
variant="ghost"
className="hover:text-destructive w-full border-none bg-white px-0 text-black"
disabled
>
<TrashIcon />
섹션 삭제
</Button>
</SectionMenu>
</SectionHeader>
<SectionContent className="flex flex-1 flex-col gap-2 overflow-y-auto">
{section.tasks.map((task: TTask) => TaskCard({ task }))}
</SectionContent>
<SectionFooter className="font-medium text-gray-600">+ 태스크 추가</SectionFooter>
<SectionFooter>
<Button
variant="ghost"
className="w-full border-none px-0 text-black"
onClick={() => handleCreateTask(section.id)}
>
<PlusIcon />
태스크 추가
</Button>
</SectionFooter>
</Section>
))}
<Button type="button" variant="outline" className="h-full w-36" disabled>
+ 섹션 추가
<PlusIcon />
섹션 추가
</Button>
</SectionWrapper>
);
Expand All @@ -61,7 +157,7 @@ function SectionCounter({ children }: { children: ReactNode }) {
);
}

function SectionMenu() {
function SectionMenu({ children }: { children: ReactNode }) {
return (
<DropdownMenu>
<DropdownMenuTrigger>
Expand All @@ -70,18 +166,7 @@ function SectionMenu() {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="gap-1 bg-white p-0">
<Button type="button" variant="ghost" className="w-full border-none px-0 text-black">
<PlusIcon />
태스크 추가
</Button>
<Button
type="button"
variant="ghost"
className="hover:text-destructive w-full border-none bg-white px-0 text-black"
>
<TrashIcon />
섹션 삭제
</Button>
{children}
</DropdownMenuContent>
</DropdownMenu>
);
Expand Down
7 changes: 5 additions & 2 deletions apps/client/src/components/KanbanContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import axios from 'axios';
import { useSuspenseQuery } from '@tanstack/react-query';
import { useParams } from '@tanstack/react-router';
import { useState } from 'react';
import { useAuth } from '@/contexts/authContext.tsx';
import Kanban from '@/components/Kanban.tsx';
import { TSection } from '@/types';
Expand All @@ -16,11 +17,13 @@ export default function KanbanContainer() {
return data.result as TSection[];
};

const { data: sections } = useSuspenseQuery({
const { data: initialSections } = useSuspenseQuery({
queryKey: ['tasks', projectId],
queryFn: () => fetchTasks(projectId),
staleTime: 1000 * 60 * 5,
});

return <Kanban sections={sections} />;
const [sections, setSections] = useState<TSection[]>(initialSections);

return <Kanban sections={sections} setSections={setSections} />;
}
1 change: 0 additions & 1 deletion apps/client/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ export type TTask = {
id: number;
title: string;
description: string;
sectionName: string;
position: string;
};

Expand Down

0 comments on commit 66bde21

Please sign in to comment.