From a0dc26719b80841e05f6ac9e13b4d44d0f8e0ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=ED=98=84=EC=A1=B0?= Date: Thu, 5 Sep 2024 17:56:56 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20=EB=A6=AC=EB=B7=B0=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20TaskList?= =?UTF-8?q?=EB=A5=BC=20=EB=8C=80=EC=B2=B4=ED=95=A0=20TaskList=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/TaskList/TaskItem.tsx | 75 +++++++++ .../components/TaskList/TaskSearchInput.tsx | 48 ++++++ .../src/components/TaskList/index.stories.tsx | 82 ++++++++++ frontend/src/components/TaskList/index.tsx | 145 ++++++++++++++++++ 4 files changed, 350 insertions(+) create mode 100644 frontend/src/components/TaskList/TaskItem.tsx create mode 100644 frontend/src/components/TaskList/TaskSearchInput.tsx create mode 100644 frontend/src/components/TaskList/index.stories.tsx create mode 100644 frontend/src/components/TaskList/index.tsx diff --git a/frontend/src/components/TaskList/TaskItem.tsx b/frontend/src/components/TaskList/TaskItem.tsx new file mode 100644 index 0000000..7f3978d --- /dev/null +++ b/frontend/src/components/TaskList/TaskItem.tsx @@ -0,0 +1,75 @@ +import { Briefcase, MessageCircle, RefreshCw, Tag, Box, Layers, Pen } from 'lucide-react'; + +interface TaskItemProps { + title: string; + createdTime: string; + creatorName: string; + project: string; + status: string; + commentsCount: number; + updatesCount: number; + lastUpdated: string; + type: 'Classification' | 'Detection' | 'Polygon' | 'Polyline'; + bgColor?: string; + memberCount: number; +} + +const typeIcons: Record<'Classification' | 'Detection' | 'Polygon' | 'Polyline', JSX.Element> = { + Classification: , + Detection: , + Polygon: , + Polyline: , +}; + +export default function TaskItem({ + title, + createdTime, + creatorName, + project, + status, + commentsCount, + updatesCount, + lastUpdated, + type, + bgColor = '#f3f4f6', + memberCount, +}: TaskItemProps) { + const icon = typeIcons[type]; + + return ( +
+
+

{title}

+

+ {createdTime} by {creatorName} +

+
+ +

{project}

+
+
+ {icon} + {type} +
+
+
+
+
{status}
+
+ + {commentsCount} +
+
+ + {updatesCount} +
+
+

Updated at {lastUpdated}

+
{`멤버 ${memberCount}명`}
+
+
+ ); +} diff --git a/frontend/src/components/TaskList/TaskSearchInput.tsx b/frontend/src/components/TaskList/TaskSearchInput.tsx new file mode 100644 index 0000000..bb486ae --- /dev/null +++ b/frontend/src/components/TaskList/TaskSearchInput.tsx @@ -0,0 +1,48 @@ +import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from '@/components/ui/select'; +import SearchInput from '@/components/ui/search-input'; +import { cn } from '@/lib/utils'; + +const sortOptions = [ + { value: 'latest', label: '최신 순' }, + { value: 'oldest', label: '오래된 순' }, + { value: 'comments', label: '댓글 많은 순' }, + { value: 'updates', label: '업데이트 많은 순' }, +]; + +interface TaskSearchInputProps { + onSearchChange: (value: string) => void; + onSortChange: (value: string) => void; + sortValue: string; +} + +export default function TaskSearchInput({ onSearchChange, onSortChange, sortValue }: TaskSearchInputProps) { + return ( +
+
+ onSearchChange(e.target.value)} + /> + +
+
+ ); +} diff --git a/frontend/src/components/TaskList/index.stories.tsx b/frontend/src/components/TaskList/index.stories.tsx new file mode 100644 index 0000000..c5bd9d5 --- /dev/null +++ b/frontend/src/components/TaskList/index.stories.tsx @@ -0,0 +1,82 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import TaskList from '.'; + +const meta: Meta = { + title: 'Components/TaskList', + component: TaskList, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + acceptedCount: 10, + rejectedCount: 5, + pendingCount: 7, + totalCount: 22, + items: [ + { + title: '갤럭시22 생산 라인 이물질 분류', + createdTime: '2 hours ago', + creatorName: 'Kim Tae Su', + project: 'Project A', + type: 'Classification', + status: 'needs_review', + commentsCount: 4, + updatesCount: 1, + lastUpdated: '1 hour ago', + memberCount: 3, + }, + { + title: '갤럭시 흠집 객체 탐지', + createdTime: '3 hours ago', + creatorName: 'Kim Tae Su', + project: 'Project B', + type: 'Detection', + status: 'completed', + commentsCount: 2, + updatesCount: 3, + lastUpdated: '30 minutes ago', + memberCount: 5, + }, + { + title: '갤럭시 흠집 경계 폴리곤', + createdTime: '5 hours ago', + creatorName: 'Kim Tae Su', + project: 'Project C', + type: 'Polygon', + status: 'in_progress', + commentsCount: 3, + updatesCount: 2, + lastUpdated: '2 hours ago', + memberCount: 4, + }, + { + title: '갤럭시 흠집 폴리라인', + createdTime: '1 day ago', + creatorName: 'Kim Tae Su', + project: 'Project D', + type: 'Polyline', + status: 'completed', + commentsCount: 5, + updatesCount: 0, + lastUpdated: '20 minutes ago', + memberCount: 6, + }, + { + title: '갤럭시 흠집 디텍션', + createdTime: '6 hours ago', + creatorName: 'Kim Tae Su', + project: 'Project E', + type: 'Detection', + status: 'pending', + commentsCount: 1, + updatesCount: 4, + lastUpdated: '3 hours ago', + memberCount: 2, + }, + ], + }, +}; diff --git a/frontend/src/components/TaskList/index.tsx b/frontend/src/components/TaskList/index.tsx new file mode 100644 index 0000000..822d64c --- /dev/null +++ b/frontend/src/components/TaskList/index.tsx @@ -0,0 +1,145 @@ +import { useState } from 'react'; +import TaskItem from './TaskItem'; +import TaskSearchInput from './TaskSearchInput'; + +interface TaskListProps { + acceptedCount: number; + rejectedCount: number; + pendingCount: number; + totalCount: number; + items: { + title: string; + createdTime: string; + creatorName: string; + project: string; + type: 'Classification' | 'Detection' | 'Polygon' | 'Polyline'; + status: string; + commentsCount: number; + updatesCount: number; + lastUpdated: string; + memberCount: number; + }[]; +} + +const typeColors: Record<'Classification' | 'Detection' | 'Polygon' | 'Polyline', string> = { + Classification: '#a2eeef', + Detection: '#d4c5f9', + Polygon: '#f9c5d4', + Polyline: '#c5f9d4', +}; + +export default function TaskList({ acceptedCount, pendingCount, totalCount, items }: TaskListProps): JSX.Element { + const [activeTab, setActiveTab] = useState('all'); + const [searchQuery, setSearchQuery] = useState(''); + const [sortValue, setSortValue] = useState('latest'); + + const filteredItems = items + .filter((item) => { + if (activeTab === 'in_progress') return item.status.toLowerCase() === 'in_progress'; + if (activeTab === 'completed') return item.status.toLowerCase() === 'completed'; + if (activeTab === 'all') return true; + return false; + }) + .filter((item) => item.title.includes(searchQuery)) + .sort((a, b) => { + switch (sortValue) { + case 'oldest': + return new Date(a.createdTime).getTime() - new Date(b.createdTime).getTime(); + case 'comments': + return b.commentsCount - a.commentsCount; + case 'updates': + return b.updatesCount - a.updatesCount; + default: + return new Date(b.createdTime).getTime() - new Date(a.createdTime).getTime(); + } + }); + + return ( +
+
+
+ + + + + +
+
+ +
+ +
+ +
+ {filteredItems.map((item, index) => ( + + ))} +
+
+ ); +}