Files
TaskTracker/TaskTracker.Web/src/components/SubtaskList.tsx
2026-02-27 00:12:23 -05:00

109 lines
3.6 KiB
TypeScript

import { useState, useRef, useEffect } from 'react'
import { Plus, Square, CheckSquare } from 'lucide-react'
import { WorkTaskStatus } from '../types/index.ts'
import type { WorkTask } from '../types/index.ts'
import { useCreateTask, useCompleteTask } from '../api/tasks.ts'
interface SubtaskListProps {
taskId: number
subtasks: WorkTask[]
}
export default function SubtaskList({ taskId, subtasks }: SubtaskListProps) {
const [showInput, setShowInput] = useState(false)
const [inputValue, setInputValue] = useState('')
const inputRef = useRef<HTMLInputElement>(null)
const createTask = useCreateTask()
const completeTask = useCompleteTask()
useEffect(() => {
if (showInput) inputRef.current?.focus()
}, [showInput])
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.key === 'Enter' && inputValue.trim()) {
createTask.mutate(
{ title: inputValue.trim(), parentTaskId: taskId },
{
onSuccess: () => {
setInputValue('')
},
}
)
}
if (e.key === 'Escape') {
setShowInput(false)
setInputValue('')
}
}
function handleToggle(subtask: WorkTask) {
if (subtask.status !== WorkTaskStatus.Completed) {
completeTask.mutate(subtask.id)
}
}
return (
<div>
<div className="flex items-center justify-between mb-3">
<h3 className="text-[11px] font-medium text-[var(--color-text-secondary)] uppercase tracking-wider">
Subtasks
</h3>
<button
onClick={() => setShowInput(true)}
className="p-1 rounded hover:bg-white/5 text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] transition-colors"
>
<Plus size={14} />
</button>
</div>
<div className="space-y-1">
{subtasks.map((subtask) => {
const isCompleted = subtask.status === WorkTaskStatus.Completed
return (
<div
key={subtask.id}
className="flex items-center gap-2 py-1.5 px-1 rounded hover:bg-white/5 cursor-pointer group"
onClick={() => handleToggle(subtask)}
>
{isCompleted ? (
<CheckSquare size={16} className="text-[var(--color-status-completed)] flex-shrink-0" />
) : (
<Square size={16} className="text-[var(--color-text-secondary)] group-hover:text-[var(--color-text-primary)] flex-shrink-0" />
)}
<span
className={`text-sm ${
isCompleted ? 'line-through text-[var(--color-text-secondary)]' : 'text-[var(--color-text-primary)]'
}`}
>
{subtask.title}
</span>
</div>
)
})}
{showInput && (
<div className="flex items-center gap-2 py-1.5 px-1">
<Square size={16} className="text-[var(--color-text-secondary)] flex-shrink-0" />
<input
ref={inputRef}
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={() => {
if (!inputValue.trim()) {
setShowInput(false)
setInputValue('')
}
}}
placeholder="New subtask..."
className="flex-1 bg-[var(--color-page)] text-sm text-[var(--color-text-primary)] px-2 py-1 rounded border border-transparent focus:border-[var(--color-accent)] outline-none placeholder-[var(--color-text-secondary)]"
/>
</div>
)}
</div>
</div>
)
}