Clean up: Remove component files

This commit is contained in:
Samuel James 2026-09-05 15:28:40 +00:00
parent 9fa73fc1f5
commit f255414f34

View File

@ -1,223 +0,0 @@
import React, { useRef } from 'react';
import { Task, TaskFilter, TaskStatus } from '../../types';
import TaskCard from './TaskCard';
import TaskRow from './TaskRow';
import './TaskList.css';
export interface FilterCounts {
my: number;
all: number;
blocked: number;
}
interface TaskListProps {
tasks: Task[];
totalTaskCount: number;
counts: FilterCounts;
currentFilter: TaskFilter;
pendingTaskId: number | null;
onFilterChange: (filter: TaskFilter) => void;
onCreateTask: () => void;
onEditTask: (task: Task) => void;
onDeleteTask: (task: Task) => void;
onStatusChange: (task: Task, status: TaskStatus) => void;
}
const FILTERS: { value: TaskFilter; label: string }[] = [
{ value: 'my', label: 'My Tasks' },
{ value: 'all', label: 'All Tasks' },
{ value: 'blocked', label: 'Blocked' },
];
const COLUMNS = ['Priority', 'Task Name', 'Assignee', 'Status', 'Due Date', 'Actions'];
const EMPTY_STATES: Record<TaskFilter, { icon: string; heading: string; body: string }> = {
my: {
icon: '\u2705',
heading: 'Nothing assigned to you',
body: 'When a teammate assigns you a task it shows up here.',
},
all: {
icon: '\u{1F4CB}',
heading: 'No tasks yet',
body: 'Get started by creating your first task.',
},
blocked: {
icon: '\u{1F6AB}',
heading: 'No blocked tasks',
body: 'Your team has no blocked work right now.',
},
};
const TaskList: React.FC<TaskListProps> = ({
tasks,
totalTaskCount,
counts,
currentFilter,
pendingTaskId,
onFilterChange,
onCreateTask,
onEditTask,
onDeleteTask,
onStatusChange,
}) => {
const tabsRef = useRef<HTMLDivElement>(null);
// Left/Right arrows move between filter tabs, as expected of an ARIA tablist.
const handleTabKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') {
return;
}
event.preventDefault();
const currentIndex = FILTERS.findIndex((filter) => filter.value === currentFilter);
const step = event.key === 'ArrowRight' ? 1 : -1;
const next = FILTERS[(currentIndex + step + FILTERS.length) % FILTERS.length];
onFilterChange(next.value);
tabsRef.current?.querySelector<HTMLButtonElement>(`#filter-tab-${next.value}`)?.focus();
};
const emptyState = EMPTY_STATES[totalTaskCount === 0 ? 'all' : currentFilter];
return (
<div className="task-list-shell">
<div className="list-toolbar">
<div
className="filter-tabs"
role="tablist"
aria-label="Filter tasks"
ref={tabsRef}
onKeyDown={handleTabKeyDown}
>
{FILTERS.map((filter) => (
<button
key={filter.value}
type="button"
id={`filter-tab-${filter.value}`}
role="tab"
className={`filter-tab${currentFilter === filter.value ? ' active' : ''}`}
aria-selected={currentFilter === filter.value}
aria-controls="task-panel"
tabIndex={currentFilter === filter.value ? 0 : -1}
/* The count badge is hidden from the name and folded into the
label, otherwise the name reads "All Tasks , 2 tasks". */
aria-label={`${filter.label}, ${counts[filter.value]} ${
counts[filter.value] === 1 ? 'task' : 'tasks'
}`}
onClick={() => onFilterChange(filter.value)}
>
{filter.label}
<span
className={`filter-count${filter.value === 'blocked' ? ' blocked' : ''}`}
aria-hidden="true"
>
{counts[filter.value]}
</span>
</button>
))}
</div>
{/* aria-label keeps the name stable when the label is hidden in the mobile FAB. */}
<button
type="button"
className="btn btn-primary create-task-btn"
onClick={onCreateTask}
aria-label="Create task"
>
<span className="create-icon" aria-hidden="true">
+
</span>
<span className="create-text">Create Task</span>
</button>
</div>
<div
className="task-panel"
id="task-panel"
role="tabpanel"
aria-labelledby={`filter-tab-${currentFilter}`}
>
{currentFilter === 'blocked' && tasks.length > 0 && (
<div className="blocked-notice">
<span className="blocked-notice-icon" aria-hidden="true">
&#9888;
</span>
<div>
<h3>These tasks are blocked</h3>
<p>
Blocked work cannot move forward yet, usually because it depends on another
unfinished task or is waiting on access. Open a task to review the details, then
reach out to the assignee.
</p>
</div>
</div>
)}
{tasks.length === 0 ? (
<div className="task-list-empty">
<div className="empty-state">
<div className="empty-icon" aria-hidden="true">
{emptyState.icon}
</div>
<h2>{emptyState.heading}</h2>
<p>{emptyState.body}</p>
{totalTaskCount === 0 && (
<button type="button" className="btn btn-primary" onClick={onCreateTask}>
Create Task
</button>
)}
</div>
</div>
) : (
<div className="task-list-container">
{/* Both views are rendered; CSS media queries decide which one is shown,
so only one reaches the accessibility tree at any width. */}
<div className="task-table" role="table" aria-label="Tasks">
<div className="task-table-header" role="row">
{COLUMNS.map((column) => (
<div
key={column}
className={`header-cell ${column.toLowerCase().replace(' ', '-')}`}
role="columnheader"
>
{/* The mockup leaves the priority heading blank. Hiding the
text (not the cell) keeps the grid columns aligned. */}
<span className={column === 'Priority' ? 'sr-only' : undefined}>{column}</span>
</div>
))}
</div>
<div className="task-table-body" role="rowgroup">
{tasks.map((task) => (
<TaskRow
key={task.id}
task={task}
busy={pendingTaskId === task.id}
onEdit={onEditTask}
onDelete={onDeleteTask}
onStatusChange={onStatusChange}
/>
))}
</div>
</div>
<div className="task-cards">
{tasks.map((task) => (
<TaskCard
key={task.id}
task={task}
busy={pendingTaskId === task.id}
onEdit={onEditTask}
onDelete={onDeleteTask}
onStatusChange={onStatusChange}
/>
))}
</div>
</div>
)}
</div>
</div>
);
};
export default TaskList;