280 lines
9.9 KiB
TypeScript
280 lines
9.9 KiB
TypeScript
'use client'
|
||
|
||
import { useState, useEffect } from 'react'
|
||
import { Task, TaskStatus } from '@/lib/mission-control/types'
|
||
|
||
interface TaskCardsPanelProps {
|
||
tasks: Task[]
|
||
toggleTask: (id: string) => void
|
||
}
|
||
|
||
const projects = ['sitemente', 'holacompi', 'arabredox', 'infrastructure', 'trading']
|
||
|
||
export function TaskCardsPanel({ tasks, toggleTask }: TaskCardsPanelProps) {
|
||
const [selectedProject, setSelectedProject] = useState<string>('sitemente')
|
||
const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null)
|
||
const [command, setCommand] = useState('')
|
||
const [responses, setResponses] = useState<{id: string, task: string, command: string, reply: string, createdAt: string}[]>([])
|
||
|
||
// Poll for replies every 10 seconds
|
||
useEffect(() => {
|
||
const pollReplies = async () => {
|
||
try {
|
||
const res = await fetch('/api/command-history')
|
||
if (res.ok) {
|
||
const data = await res.json()
|
||
// Get entries with replies that haven't been shown
|
||
const withReplies = (data.history || []).filter((h: any) => h.reply && h.reply.length > 0)
|
||
setResponses(withReplies.slice(-5).reverse())
|
||
}
|
||
} catch (e) {}
|
||
}
|
||
|
||
pollReplies()
|
||
const interval = setInterval(pollReplies, 10000)
|
||
return () => clearInterval(interval)
|
||
}, [])
|
||
|
||
const projectTasks = tasks.filter(t =>
|
||
t.project === selectedProject &&
|
||
(t.status === 'todo' || t.status === 'in_progress')
|
||
)
|
||
|
||
const handleTaskClick = (taskId: string) => {
|
||
setSelectedTaskId(selectedTaskId === taskId ? null : taskId)
|
||
setCommand('')
|
||
}
|
||
|
||
const handleConfirm = () => {
|
||
if (!selectedTaskId || !command.trim()) return
|
||
|
||
const task = tasks.find(t => t.id === selectedTaskId)
|
||
if (!task) return
|
||
|
||
fetch('/api/command-history', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
task: task.title,
|
||
command: command,
|
||
project: selectedProject,
|
||
action: 'continue-task'
|
||
})
|
||
}).catch(() => {})
|
||
|
||
fetch('/api/command', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
command: command,
|
||
task: task.title,
|
||
action: 'continue-task'
|
||
})
|
||
}).catch(() => {})
|
||
|
||
alert(`✅ Sent to Horus: "${command}"`)
|
||
setCommand('')
|
||
setSelectedTaskId(null)
|
||
}
|
||
|
||
const handleQuickChat = () => {
|
||
if (!command.trim()) return
|
||
|
||
// Save quick chat to history - this will notify Horus via the API
|
||
fetch('/api/command-history', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
task: `Quick message - ${selectedProject}`,
|
||
command: command,
|
||
project: selectedProject,
|
||
action: 'quick-message'
|
||
})
|
||
}).catch(() => {})
|
||
|
||
alert(`✅ Sent to Horus! I'll reply shortly.`)
|
||
setCommand('')
|
||
}
|
||
|
||
const selectedTask = tasks.find(t => t.id === selectedTaskId)
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
{/* Project Tabs */}
|
||
<div className="flex gap-2 flex-wrap">
|
||
{projects.map(project => {
|
||
const count = tasks.filter(t =>
|
||
t.project === project &&
|
||
(t.status === 'todo' || t.status === 'in_progress')
|
||
).length
|
||
|
||
return (
|
||
<button
|
||
key={project}
|
||
onClick={() => {
|
||
setSelectedProject(project)
|
||
setSelectedTaskId(null)
|
||
}}
|
||
className={`px-4 py-2 rounded-lg text-sm font-medium transition ${
|
||
selectedProject === project
|
||
? 'bg-brand-pink text-white'
|
||
: 'bg-white/10 text-white/70 hover:bg-white/20'
|
||
}`}
|
||
>
|
||
{project.charAt(0).toUpperCase() + project.slice(1)}
|
||
<span className="ml-2 text-xs opacity-70">({count})</span>
|
||
</button>
|
||
)
|
||
})}
|
||
</div>
|
||
|
||
{/* Task Cards */}
|
||
<div className="border border-white/20 rounded-lg p-4 bg-white/5">
|
||
<h3 className="text-lg font-bold mb-4">
|
||
📋 {selectedProject.toUpperCase()} TASKS ({projectTasks.length})
|
||
</h3>
|
||
|
||
<div className="space-y-2">
|
||
{projectTasks.map((task) => (
|
||
<div
|
||
key={task.id}
|
||
className={`p-3 rounded-lg border transition-all ${
|
||
selectedTaskId === task.id
|
||
? 'border-brand-pink bg-brand-pink/10'
|
||
: 'border-white/10 bg-white/5 hover:border-white/30'
|
||
}`}
|
||
onClick={() => handleTaskClick(task.id)}
|
||
>
|
||
<div className="flex items-center gap-3">
|
||
<button
|
||
onClick={(e) => {
|
||
e.stopPropagation()
|
||
toggleTask(task.id)
|
||
}}
|
||
className={`w-5 h-5 rounded-full border-2 flex items-center justify-center transition ${
|
||
task.status === 'done'
|
||
? 'border-green-500 bg-green-500'
|
||
: task.status === 'in_progress'
|
||
? 'border-yellow-500 bg-yellow-500'
|
||
: 'border-white/30 hover:border-white/50'
|
||
}`}
|
||
>
|
||
{task.status === 'done' && (
|
||
<svg className="w-3 h-3 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
|
||
</svg>
|
||
)}
|
||
</button>
|
||
|
||
<div className="flex-1">
|
||
<p className={`font-medium ${task.status === 'done' ? 'line-through opacity-50' : ''}`}>
|
||
{task.title}
|
||
</p>
|
||
{task.description && (
|
||
<p className="text-xs text-white/50 truncate">{task.description}</p>
|
||
)}
|
||
</div>
|
||
|
||
<span className="text-lg">
|
||
{selectedTaskId === task.id ? '▼' : '▶'}
|
||
</span>
|
||
</div>
|
||
|
||
{/* Expanded Input */}
|
||
{selectedTaskId === task.id && (
|
||
<div className="mt-3 ml-8">
|
||
<textarea
|
||
value={command}
|
||
onChange={(e) => setCommand(e.target.value)}
|
||
placeholder={`What to do with "${task.title}"?`}
|
||
className="w-full px-3 py-2 bg-black/50 border border-white/20 rounded text-sm text-white placeholder:text-white/40 focus:outline-none focus:border-brand-pink resize-none"
|
||
rows={3}
|
||
onClick={(e) => e.stopPropagation()}
|
||
autoFocus
|
||
/>
|
||
<div className="flex gap-2 mt-2">
|
||
<button
|
||
onClick={(e) => {
|
||
e.stopPropagation()
|
||
handleConfirm()
|
||
}}
|
||
disabled={!command.trim()}
|
||
className={`px-4 py-1.5 rounded text-xs font-bold ${
|
||
command.trim()
|
||
? 'bg-brand-pink text-white hover:bg-[#ff7bc0]'
|
||
: 'bg-white/10 text-white/30 cursor-not-allowed'
|
||
}`}
|
||
>
|
||
▶ SEND TO HORUS
|
||
</button>
|
||
<button
|
||
onClick={(e) => {
|
||
e.stopPropagation()
|
||
setSelectedTaskId(null)
|
||
setCommand('')
|
||
}}
|
||
className="px-3 py-1.5 text-xs text-white/60 hover:text-white"
|
||
>
|
||
Cancel
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{projectTasks.length === 0 && (
|
||
<p className="text-white/50 text-center py-4">No pending tasks ✓</p>
|
||
)}
|
||
|
||
{/* Quick Chat - always available */}
|
||
<div className="mt-4 pt-4 border-t border-white/10">
|
||
<p className="text-xs text-white/50 mb-2">💬 Quick message for {selectedProject}</p>
|
||
<div className="flex gap-2">
|
||
<textarea
|
||
value={command}
|
||
onChange={(e) => setCommand(e.target.value)}
|
||
placeholder={`New task or message for ${selectedProject}...`}
|
||
className="flex-1 px-3 py-2 bg-black/50 border border-white/20 rounded text-sm text-white placeholder:text-white/40 focus:outline-none focus:border-brand-pink resize-none"
|
||
rows={2}
|
||
onKeyDown={(e) => {
|
||
if (e.key === 'Enter' && !e.shiftKey) {
|
||
e.preventDefault()
|
||
handleQuickChat()
|
||
}
|
||
}}
|
||
/>
|
||
<button
|
||
onClick={handleQuickChat}
|
||
disabled={!command.trim()}
|
||
className={`px-4 rounded text-sm font-bold ${
|
||
command.trim()
|
||
? 'bg-brand-pink text-white hover:bg-[#ff7bc0]'
|
||
: 'bg-white/10 text-white/30 cursor-not-allowed'
|
||
}`}
|
||
>
|
||
➤
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Responses from Horus */}
|
||
{responses.length > 0 && (
|
||
<div className="mt-4 pt-4 border-t border-white/10">
|
||
<p className="text-xs text-white/50 mb-2">💬 Recent replies from Horus</p>
|
||
<div className="space-y-2 max-h-40 overflow-y-auto">
|
||
{responses.map((r) => (
|
||
<div key={r.id} className="p-2 rounded bg-brand-pink/10 border border-brand-pink/30 text-sm">
|
||
<p className="text-white/60 text-xs">You: {r.command}</p>
|
||
<p className="text-brand-pink mt-1">👁️ {r.reply}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|