109 lines
3.6 KiB
TypeScript
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>
|
|
)
|
|
}
|