import { useState, useCallback } from "react";
export interface ParsedTask {
name: string;
description?: string;
group?: string;
order: number;
completed: boolean;
dependencies: string[];
}
interface TaskDerivationPreviewProps {
tasks: ParsedTask[];
groups: string[];
fileName: string;
onCreateTasks: (selectedTasks: ParsedTask[]) => void;
onCancel: () => void;
loading?: boolean;
}
export function TaskDerivationPreview({
tasks,
groups,
fileName,
onCreateTasks,
onCancel,
loading = false,
}: TaskDerivationPreviewProps) {
const [selectedIndices, setSelectedIndices] = useState<Set<number>>(
new Set(tasks.map((_, i) => i)) // Select all by default
);
const toggleTask = useCallback((index: number) => {
setSelectedIndices((prev) => {
const newSet = new Set(prev);
if (newSet.has(index)) {
newSet.delete(index);
} else {
newSet.add(index);
}
return newSet;
});
}, []);
const selectAll = useCallback(() => {
setSelectedIndices(new Set(tasks.map((_, i) => i)));
}, [tasks]);
const selectNone = useCallback(() => {
setSelectedIndices(new Set());
}, []);
const handleCreate = useCallback(() => {
const selectedTasks = tasks.filter((_, i) => selectedIndices.has(i));
onCreateTasks(selectedTasks);
}, [tasks, selectedIndices, onCreateTasks]);
// Group tasks by their group property
const tasksByGroup = tasks.reduce((acc, task, index) => {
const groupKey = task.group || "Ungrouped";
if (!acc[groupKey]) {
acc[groupKey] = [];
}
acc[groupKey].push({ task, index });
return acc;
}, {} as Record<string, { task: ParsedTask; index: number }[]>);
const selectedCount = selectedIndices.size;
const totalCount = tasks.length;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div className="w-full max-w-2xl p-6 bg-[#0a1628] border border-[rgba(117,170,252,0.3)] max-h-[80vh] flex flex-col">
{/* Header */}
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="font-mono text-sm text-[#75aafc] uppercase">
Create Tasks from Document
</h3>
<p className="font-mono text-xs text-[#555] mt-1">
Source: {fileName}
</p>
</div>
<div className="flex items-center gap-2">
<button
onClick={selectAll}
className="font-mono text-[10px] text-[#75aafc] hover:text-[#9bc3ff] transition-colors"
>
Select All
</button>
<span className="text-[#555]">|</span>
<button
onClick={selectNone}
className="font-mono text-[10px] text-[#75aafc] hover:text-[#9bc3ff] transition-colors"
>
Select None
</button>
</div>
</div>
{/* Task List */}
<div className="flex-1 overflow-y-auto space-y-4 mb-4">
{groups.length > 0 ? (
// Grouped view
Object.entries(tasksByGroup).map(([groupName, groupTasks]) => (
<div key={groupName} className="space-y-2">
<h4 className="font-mono text-xs text-[#9bc3ff] uppercase border-b border-[rgba(117,170,252,0.2)] pb-1">
{groupName}
</h4>
{groupTasks.map(({ task, index }) => (
<TaskItem
key={index}
task={task}
index={index}
selected={selectedIndices.has(index)}
onToggle={() => toggleTask(index)}
/>
))}
</div>
))
) : (
// Flat view
tasks.map((task, index) => (
<TaskItem
key={index}
task={task}
index={index}
selected={selectedIndices.has(index)}
onToggle={() => toggleTask(index)}
/>
))
)}
</div>
{/* Footer */}
<div className="flex items-center justify-between pt-4 border-t border-[rgba(117,170,252,0.2)]">
<span className="font-mono text-xs text-[#555]">
{selectedCount} of {totalCount} tasks selected
</span>
<div className="flex gap-2">
<button
onClick={onCancel}
disabled={loading}
className="px-4 py-2 font-mono text-xs text-[#9bc3ff] hover:text-[#dbe7ff] transition-colors disabled:opacity-50"
>
Cancel
</button>
<button
onClick={handleCreate}
disabled={loading || selectedCount === 0}
className="px-4 py-2 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading ? "Creating..." : `Create ${selectedCount} Task${selectedCount !== 1 ? "s" : ""}`}
</button>
</div>
</div>
{/* Chaining info */}
{selectedCount > 1 && (
<p className="font-mono text-[10px] text-[#555] mt-2 text-center">
Tasks will be chained: each task continues from the previous one's work
</p>
)}
</div>
</div>
);
}
function TaskItem({
task,
index,
selected,
onToggle,
}: {
task: ParsedTask;
index: number;
selected: boolean;
onToggle: () => void;
}) {
return (
<button
onClick={onToggle}
className={`w-full text-left p-3 border transition-colors ${
selected
? "border-[#75aafc] bg-[rgba(117,170,252,0.1)]"
: "border-[rgba(117,170,252,0.15)] hover:border-[rgba(117,170,252,0.3)]"
}`}
>
<div className="flex items-start gap-2">
<span
className={`font-mono text-xs mt-0.5 ${
selected ? "text-[#75aafc]" : "text-[#555]"
}`}
>
{selected ? "[x]" : "[ ]"}
</span>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<span className="font-mono text-[10px] text-[#555]">#{index + 1}</span>
<span className="font-mono text-sm text-[#dbe7ff]">{task.name}</span>
{task.completed && (
<span className="font-mono text-[9px] text-green-400 uppercase">
done in source
</span>
)}
</div>
{task.description && (
<p className="font-mono text-xs text-[#555] mt-1 truncate">
{task.description}
</p>
)}
{task.dependencies.length > 0 && (
<p className="font-mono text-[10px] text-[#75aafc] mt-1">
Depends on: {task.dependencies.join(", ")}
</p>
)}
</div>
</div>
</button>
);
}