Refactor: 이름 변경 파일 등 삭제
This commit is contained in:
parent
e754d6b31f
commit
d0951d0ede
@ -1,39 +0,0 @@
|
|||||||
import '@/index.css';
|
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
|
||||||
import ReviewRequestItem from './ReviewRequestItem';
|
|
||||||
|
|
||||||
const meta: Meta<typeof ReviewRequestItem> = {
|
|
||||||
title: 'Components/ReviewRequestItem',
|
|
||||||
component: ReviewRequestItem,
|
|
||||||
argTypes: {
|
|
||||||
title: { control: 'text', description: 'Title of the review request' },
|
|
||||||
createdTime: { control: 'text', description: 'Time when the review was created' },
|
|
||||||
creatorName: { control: 'text', description: 'Name of the creator' },
|
|
||||||
project: { control: 'text', description: 'Project name' },
|
|
||||||
status: { control: 'text', description: 'Status of the review request' },
|
|
||||||
commentsCount: { control: 'number', description: 'Number of comments' },
|
|
||||||
updatesCount: { control: 'number', description: 'Number of updates' },
|
|
||||||
lastUpdated: { control: 'text', description: 'Time when the review was last updated' },
|
|
||||||
type: {
|
|
||||||
control: 'object',
|
|
||||||
description: 'Label for the request type with text and color',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof ReviewRequestItem>;
|
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
args: {
|
|
||||||
title: 'Feat: 워크스페이스 멤버 추가 구현 - S11P21S002-37',
|
|
||||||
createdTime: '!24 · created 2 hours ago',
|
|
||||||
creatorName: '김태수',
|
|
||||||
project: 'be/develop',
|
|
||||||
status: 'Merged',
|
|
||||||
commentsCount: 4,
|
|
||||||
updatesCount: 1,
|
|
||||||
lastUpdated: 'updated 1 hour ago',
|
|
||||||
type: { text: 'Classification', color: '#a2eeef' },
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,80 +0,0 @@
|
|||||||
import { Briefcase, MessageCircle, RefreshCw, Tag, Box, Layers, Pen } from 'lucide-react';
|
|
||||||
|
|
||||||
interface ReviewRequestItemProps {
|
|
||||||
title: string;
|
|
||||||
createdTime: string;
|
|
||||||
creatorName: string;
|
|
||||||
project: string;
|
|
||||||
status: string;
|
|
||||||
commentsCount: number;
|
|
||||||
updatesCount: number;
|
|
||||||
lastUpdated: string;
|
|
||||||
type: { text: 'Classification' | 'Detection' | 'Polygon' | 'Polyline'; color: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
const typeIcons: Record<'Classification' | 'Detection' | 'Polygon' | 'Polyline', JSX.Element> = {
|
|
||||||
Classification: <Tag className="h-4 w-4 text-white" />,
|
|
||||||
Detection: <Box className="h-4 w-4 text-white" />,
|
|
||||||
Polygon: <Layers className="h-4 w-4 text-white" />,
|
|
||||||
Polyline: <Pen className="h-4 w-4 text-white" />,
|
|
||||||
};
|
|
||||||
|
|
||||||
const typeStyles: Record<'Classification' | 'Detection' | 'Polygon' | 'Polyline', string> = {
|
|
||||||
Classification: '#a2eeef',
|
|
||||||
Detection: '#d4c5f9',
|
|
||||||
Polygon: '#f9c5d4',
|
|
||||||
Polyline: '#c5f9d4',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ReviewRequestItem({
|
|
||||||
title,
|
|
||||||
createdTime,
|
|
||||||
creatorName,
|
|
||||||
project,
|
|
||||||
status,
|
|
||||||
commentsCount,
|
|
||||||
updatesCount,
|
|
||||||
lastUpdated,
|
|
||||||
type,
|
|
||||||
}: ReviewRequestItemProps) {
|
|
||||||
const icon = typeIcons[type.text];
|
|
||||||
const bgColor = typeStyles[type.text];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex h-[100px] w-full items-center justify-between border-b-[0.67px] border-[#ececef] bg-[#fbfafd] p-4">
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<p className="text-sm font-semibold text-[#333238]">{title}</p>
|
|
||||||
<p className="mt-1 text-xs text-[#737278]">
|
|
||||||
{createdTime} by {creatorName}
|
|
||||||
</p>
|
|
||||||
<div className="mt-1 flex items-center">
|
|
||||||
<Briefcase className="h-3 w-3 text-[#737278]" />
|
|
||||||
<p className="ml-1 text-xs text-[#737278]">{project}</p>
|
|
||||||
</div>
|
|
||||||
{type && (
|
|
||||||
<div
|
|
||||||
className="mt-1 inline-flex max-w-fit items-center gap-1 rounded-full px-3 py-1 text-xs text-white"
|
|
||||||
style={{ backgroundColor: bgColor, padding: '1px 5px' }}
|
|
||||||
>
|
|
||||||
{icon}
|
|
||||||
<span className="overflow-hidden text-ellipsis whitespace-nowrap">{type.text}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col items-end gap-1">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="rounded-full bg-[#cbe2f9] px-3 py-0.5 text-center text-xs text-[#0b5cad]">{status}</div>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<MessageCircle className="h-4 w-4 text-[#737278]" />
|
|
||||||
<span className="text-sm">{commentsCount}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<RefreshCw className="h-4 w-4 text-[#737278]" />
|
|
||||||
<span className="text-sm">{updatesCount}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-[#737278]">{lastUpdated}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
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 ReviewSearchInputProps {
|
|
||||||
onSearchChange: (value: string) => void;
|
|
||||||
onSortChange: (value: string) => void;
|
|
||||||
sortValue: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ReviewSearchInput({ onSearchChange, onSortChange, sortValue }: ReviewSearchInputProps) {
|
|
||||||
return (
|
|
||||||
<div className={cn('flex w-full items-center justify-between border-b-[0.67px] border-[#ececef] bg-[#fbfafd] p-4')}>
|
|
||||||
<div className="flex flex-1 items-center gap-4">
|
|
||||||
<SearchInput
|
|
||||||
className="flex-1"
|
|
||||||
placeholder="검색 또는 필터..."
|
|
||||||
onChange={(e) => onSearchChange(e.target.value)}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
value={sortValue}
|
|
||||||
onValueChange={onSortChange}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="w-[180px] rounded-md border border-gray-200">
|
|
||||||
<SelectValue placeholder="정렬 기준 선택" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{sortOptions.map((option) => (
|
|
||||||
<SelectItem
|
|
||||||
key={option.value}
|
|
||||||
value={option.value}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,78 +0,0 @@
|
|||||||
import '@/index.css';
|
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
|
||||||
import ReviewRequest from '.';
|
|
||||||
|
|
||||||
const meta: Meta<typeof ReviewRequest> = {
|
|
||||||
title: 'Components/ReviewRequest',
|
|
||||||
component: ReviewRequest,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
|
|
||||||
type Story = StoryObj<typeof ReviewRequest>;
|
|
||||||
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '갤럭시 흠집 객체 탐지',
|
|
||||||
createdTime: '3 hours ago',
|
|
||||||
creatorName: 'Kim Tae Su',
|
|
||||||
project: 'Project B',
|
|
||||||
type: 'Detection',
|
|
||||||
status: 'completed',
|
|
||||||
commentsCount: 2,
|
|
||||||
updatesCount: 3,
|
|
||||||
lastUpdated: '30 minutes ago',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '갤럭시 흠집 폴리라인',
|
|
||||||
createdTime: '1 day ago',
|
|
||||||
creatorName: 'Kim Tae Su',
|
|
||||||
project: 'Project D',
|
|
||||||
type: 'Polyline',
|
|
||||||
status: 'completed',
|
|
||||||
commentsCount: 5,
|
|
||||||
updatesCount: 0,
|
|
||||||
lastUpdated: '20 minutes ago',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '갤럭시 흠집 디텍션 허가 요청',
|
|
||||||
createdTime: '6 hours ago',
|
|
||||||
creatorName: 'Kim Tae Su',
|
|
||||||
project: 'Project E',
|
|
||||||
type: 'Detection',
|
|
||||||
status: 'pending',
|
|
||||||
commentsCount: 1,
|
|
||||||
updatesCount: 4,
|
|
||||||
lastUpdated: '3 hours ago',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,151 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import ReviewRequestItem from './ReviewRequestItem';
|
|
||||||
import ReviewSearchInput from './ReviewSearchInput';
|
|
||||||
|
|
||||||
interface ReviewRequestProps {
|
|
||||||
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;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const typeColors: Record<'Classification' | 'Detection' | 'Polygon' | 'Polyline', string> = {
|
|
||||||
Classification: '#a2eeef',
|
|
||||||
Detection: '#d4c5f9',
|
|
||||||
Polygon: '#f9c5d4',
|
|
||||||
Polyline: '#c5f9d4',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ReviewRequest({
|
|
||||||
acceptedCount,
|
|
||||||
rejectedCount,
|
|
||||||
pendingCount,
|
|
||||||
totalCount,
|
|
||||||
items,
|
|
||||||
}: ReviewRequestProps): JSX.Element {
|
|
||||||
const [activeTab, setActiveTab] = useState('pending');
|
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
|
||||||
const [sortValue, setSortValue] = useState('latest');
|
|
||||||
|
|
||||||
const filteredItems = items
|
|
||||||
.filter((item) => {
|
|
||||||
if (activeTab === 'pending') return item.status.toLowerCase() === 'needs_review';
|
|
||||||
if (activeTab === 'accepted') return item.status.toLowerCase() === 'completed';
|
|
||||||
if (activeTab === 'rejected') return ['in_progress', 'pending'].includes(item.status.toLowerCase());
|
|
||||||
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 (
|
|
||||||
<div className="relative w-full">
|
|
||||||
<div className="relative w-full px-4">
|
|
||||||
<div className="flex w-full items-center border-b-[0.67px] border-solid border-[#dcdcde]">
|
|
||||||
<button
|
|
||||||
className={`flex h-12 w-[100px] items-center justify-between px-3 ${
|
|
||||||
activeTab === 'pending' ? 'shadow-[inset_0px_-2px_0px_#1f75cb]' : ''
|
|
||||||
}`}
|
|
||||||
onClick={() => setActiveTab('pending')}
|
|
||||||
>
|
|
||||||
<span className={`text-sm ${activeTab === 'pending' ? 'font-semibold' : 'font-normal'} text-[#333238]`}>
|
|
||||||
요청
|
|
||||||
</span>
|
|
||||||
<span className="flex h-4 w-6 items-center justify-center rounded-[160px] bg-[#ececef] text-xs text-[#626168]">
|
|
||||||
{pendingCount}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className={`flex h-12 w-[100px] items-center justify-between px-3 ${
|
|
||||||
activeTab === 'accepted' ? 'shadow-[inset_0px_-2px_0px_#1f75cb]' : ''
|
|
||||||
}`}
|
|
||||||
onClick={() => setActiveTab('accepted')}
|
|
||||||
>
|
|
||||||
<span className={`text-sm ${activeTab === 'accepted' ? 'font-semibold' : 'font-normal'} text-[#333238]`}>
|
|
||||||
승인
|
|
||||||
</span>
|
|
||||||
<span className="flex h-4 w-6 items-center justify-center rounded-[160px] bg-[#ececef] text-xs text-[#626168]">
|
|
||||||
{acceptedCount}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className={`flex h-12 w-[100px] items-center justify-between px-3 ${
|
|
||||||
activeTab === 'rejected' ? 'shadow-[inset_0px_-2px_0px_#1f75cb]' : ''
|
|
||||||
}`}
|
|
||||||
onClick={() => setActiveTab('rejected')}
|
|
||||||
>
|
|
||||||
<span className={`text-sm ${activeTab === 'rejected' ? 'font-semibold' : 'font-normal'} text-[#333238]`}>
|
|
||||||
거부
|
|
||||||
</span>
|
|
||||||
<span className="flex h-4 w-6 items-center justify-center rounded-[160px] bg-[#ececef] text-xs text-[#626168]">
|
|
||||||
{rejectedCount}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className={`flex h-12 w-[100px] items-center justify-between px-3 ${
|
|
||||||
activeTab === 'all' ? 'shadow-[inset_0px_-2px_0px_#1f75cb]' : ''
|
|
||||||
}`}
|
|
||||||
onClick={() => setActiveTab('all')}
|
|
||||||
>
|
|
||||||
<span className={`text-sm ${activeTab === 'all' ? 'font-semibold' : 'font-normal'} text-[#333238]`}>
|
|
||||||
전체
|
|
||||||
</span>
|
|
||||||
<span className="flex h-4 w-6 items-center justify-center rounded-[160px] bg-[#ececef] text-xs text-[#626168]">
|
|
||||||
{totalCount}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="relative w-full px-4">
|
|
||||||
<ReviewSearchInput
|
|
||||||
onSearchChange={setSearchQuery}
|
|
||||||
onSortChange={setSortValue}
|
|
||||||
sortValue={sortValue}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="relative w-full overflow-y-auto px-4">
|
|
||||||
{filteredItems.map((item, index) => (
|
|
||||||
<ReviewRequestItem
|
|
||||||
key={index}
|
|
||||||
title={item.title}
|
|
||||||
createdTime={item.createdTime}
|
|
||||||
creatorName={item.creatorName}
|
|
||||||
project={item.project}
|
|
||||||
status={item.status}
|
|
||||||
commentsCount={item.commentsCount}
|
|
||||||
updatesCount={item.updatesCount}
|
|
||||||
lastUpdated={item.lastUpdated}
|
|
||||||
type={{ text: item.type, color: typeColors[item.type] }}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user