131 lines
3.3 KiB
TypeScript
131 lines
3.3 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState } from 'react';
|
|
import styled from 'styled-components';
|
|
import TaskBadge from '../common/TaskBadge';
|
|
|
|
type TaskStatus = 'pending' | 'in_progress' | 'review' | 'done' | 'failed' | 'blocked' | 'escalated';
|
|
|
|
interface Task {
|
|
id: number;
|
|
taskId: string;
|
|
title: string;
|
|
assignee: string;
|
|
status: TaskStatus;
|
|
iteration: number;
|
|
createdAt: string;
|
|
}
|
|
|
|
const ALL_STATUSES = ['all', 'pending', 'in_progress', 'review', 'done', 'failed', 'blocked', 'escalated'];
|
|
|
|
const Wrapper = styled.div``;
|
|
|
|
const Filters = styled.div`
|
|
display: flex;
|
|
gap: 6px;
|
|
margin-bottom: 16px;
|
|
flex-wrap: wrap;
|
|
`;
|
|
|
|
const FilterBtn = styled.button<{ $active: boolean }>`
|
|
padding: 4px 10px;
|
|
border-radius: 6px;
|
|
border: 1px solid ${({ $active }) => $active ? '#58A6FF' : 'var(--border-color)'};
|
|
background: ${({ $active }) => $active ? 'rgba(88,166,255,0.12)' : 'transparent'};
|
|
color: ${({ $active }) => $active ? '#58A6FF' : 'var(--text-secondary)'};
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
transition: all 0.15s;
|
|
|
|
&:hover { border-color: #58A6FF; color: #58A6FF; }
|
|
`;
|
|
|
|
const Table = styled.table`
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
`;
|
|
|
|
const Th = styled.th`
|
|
text-align: left;
|
|
padding: 8px 12px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
border-bottom: 1px solid var(--border-color);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
`;
|
|
|
|
const Td = styled.td`
|
|
padding: 10px 12px;
|
|
font-size: 13px;
|
|
border-bottom: 1px solid var(--border-color);
|
|
color: var(--text-primary);
|
|
`;
|
|
|
|
const Tr = styled.tr`
|
|
&:last-child td { border-bottom: none; }
|
|
&:hover td { background: rgba(240,246,252,0.03); }
|
|
`;
|
|
|
|
const TaskIdCell = styled.code`
|
|
color: #58A6FF;
|
|
font-family: monospace;
|
|
font-size: 12px;
|
|
`;
|
|
|
|
const IterationCell = styled.span<{ $count: number }>`
|
|
color: ${({ $count }) => $count > 0 ? '#FF9800' : 'var(--text-secondary)'};
|
|
`;
|
|
|
|
const Empty = styled.div`
|
|
padding: 32px;
|
|
text-align: center;
|
|
color: var(--text-secondary);
|
|
font-size: 13px;
|
|
`;
|
|
|
|
export default function TaskTable({ tasks }: { tasks: Task[] }) {
|
|
const [filter, setFilter] = useState('all');
|
|
|
|
const filtered = filter === 'all' ? tasks : tasks.filter((t) => t.status === filter);
|
|
|
|
return (
|
|
<Wrapper>
|
|
<Filters>
|
|
{ALL_STATUSES.map((s) => (
|
|
<FilterBtn key={s} $active={filter === s} onClick={() => setFilter(s)}>
|
|
{s === 'all' ? '전체' : s}
|
|
</FilterBtn>
|
|
))}
|
|
</Filters>
|
|
{filtered.length === 0 ? (
|
|
<Empty>해당 상태의 태스크 없음</Empty>
|
|
) : (
|
|
<Table>
|
|
<thead>
|
|
<tr>
|
|
<Th>ID</Th>
|
|
<Th>제목</Th>
|
|
<Th>담당</Th>
|
|
<Th>상태</Th>
|
|
<Th>반복</Th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{filtered.map((task) => (
|
|
<Tr key={task.id}>
|
|
<Td><TaskIdCell>{task.taskId}</TaskIdCell></Td>
|
|
<Td>{task.title}</Td>
|
|
<Td>{task.assignee}</Td>
|
|
<Td><TaskBadge status={task.status} /></Td>
|
|
<Td><IterationCell $count={task.iteration}>{task.iteration || '-'}</IterationCell></Td>
|
|
</Tr>
|
|
))}
|
|
</tbody>
|
|
</Table>
|
|
)}
|
|
</Wrapper>
|
|
);
|
|
}
|