feat(web): project detail views — missions, tasks, PRD viewer (#122) (#140)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]> Co-committed-by: Jason Woltje <[email protected]>
This commit was merged in pull request #140.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
'use client';
|
||||
|
||||
import { cn } from '@/lib/cn';
|
||||
import type { Task, TaskStatus } from '@/lib/types';
|
||||
|
||||
interface TaskStatusSummaryProps {
|
||||
tasks: Task[];
|
||||
activeFilter: TaskStatus | 'all';
|
||||
onFilterChange: (filter: TaskStatus | 'all') => void;
|
||||
}
|
||||
|
||||
const statusConfig: {
|
||||
id: TaskStatus | 'all';
|
||||
label: string;
|
||||
color: string;
|
||||
activeColor: string;
|
||||
}[] = [
|
||||
{
|
||||
id: 'all',
|
||||
label: 'All',
|
||||
color: 'text-text-muted',
|
||||
activeColor: 'bg-surface-elevated text-text-primary',
|
||||
},
|
||||
{
|
||||
id: 'not-started',
|
||||
label: 'Not Started',
|
||||
color: 'text-gray-400',
|
||||
activeColor: 'bg-gray-600/20 text-gray-300',
|
||||
},
|
||||
{
|
||||
id: 'in-progress',
|
||||
label: 'In Progress',
|
||||
color: 'text-blue-400',
|
||||
activeColor: 'bg-blue-600/20 text-blue-400',
|
||||
},
|
||||
{
|
||||
id: 'blocked',
|
||||
label: 'Blocked',
|
||||
color: 'text-error',
|
||||
activeColor: 'bg-error/20 text-error',
|
||||
},
|
||||
{
|
||||
id: 'done',
|
||||
label: 'Done',
|
||||
color: 'text-success',
|
||||
activeColor: 'bg-success/20 text-success',
|
||||
},
|
||||
];
|
||||
|
||||
export function TaskStatusSummary({
|
||||
tasks,
|
||||
activeFilter,
|
||||
onFilterChange,
|
||||
}: TaskStatusSummaryProps): React.ReactElement {
|
||||
const counts: Record<TaskStatus | 'all', number> = {
|
||||
all: tasks.length,
|
||||
'not-started': 0,
|
||||
'in-progress': 0,
|
||||
blocked: 0,
|
||||
done: 0,
|
||||
cancelled: 0,
|
||||
};
|
||||
|
||||
for (const task of tasks) {
|
||||
counts[task.status] = (counts[task.status] ?? 0) + 1;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{statusConfig.map((config) => {
|
||||
const count = counts[config.id];
|
||||
const isActive = activeFilter === config.id;
|
||||
return (
|
||||
<button
|
||||
key={config.id}
|
||||
type="button"
|
||||
onClick={() => onFilterChange(config.id)}
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs transition-colors',
|
||||
isActive
|
||||
? cn('border-transparent', config.activeColor)
|
||||
: 'border-surface-border text-text-muted hover:border-gray-500',
|
||||
)}
|
||||
>
|
||||
<span>{config.label}</span>
|
||||
<span
|
||||
className={cn(
|
||||
'rounded-full px-1.5 py-0.5 text-xs font-medium',
|
||||
isActive ? 'bg-black/20' : 'bg-surface-elevated',
|
||||
)}
|
||||
>
|
||||
{count}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user