Skip to main content
Glama
TaskEditView.jsx10.9 kB
import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; function TaskEditView({ task, onBack, projectRoot, profileId, onNavigateToTask, taskIndex, allTasks, onSave }) { const { t } = useTranslation(); const [editedTask, setEditedTask] = useState({ description: task.description || '', notes: task.notes || '', implementationGuide: task.implementationGuide || '', verificationCriteria: task.verificationCriteria || '', agent: task.agent || '' }); const [isSaving, setIsSaving] = useState(false); const [error, setError] = useState(null); const [availableAgents, setAvailableAgents] = useState([]); // Load available agents on mount useEffect(() => { const loadAgents = async () => { if (!profileId) return; try { const response = await fetch(`/api/agents/combined/${profileId}`); if (response.ok) { const agents = await response.json(); setAvailableAgents(agents); } } catch (err) { console.error('Error loading agents:', err); } }; loadAgents(); }, [profileId]); const formatDate = (dateStr) => { if (!dateStr) return '—'; const date = new Date(dateStr); return date.toLocaleString(undefined, { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true }); }; const getStatusColor = (status) => { const colors = { pending: '#ffa500', in_progress: '#3498db', completed: '#27ae60', blocked: '#e74c3c' }; return colors[status] || '#666'; }; const getFileTypeIcon = (type) => { const icons = { CREATE: '➕', TO_MODIFY: '✏️', REFERENCE: '📖', DEPENDENCY: '🔗', OTHER: '📄' }; return icons[type] || '📄'; }; const handleInputChange = (field, value) => { setEditedTask(prev => ({ ...prev, [field]: value })); }; const handleSave = async () => { setIsSaving(true); setError(null); console.log('Saving task with profileId:', profileId); console.log('Task data:', { taskId: task.id, updates: editedTask }); // Check if profileId is provided if (!profileId) { setError('Profile ID is missing. Please try again.'); setIsSaving(false); return; } try { const url = `/api/tasks/${profileId}/update`; console.log('Calling API:', url); const response = await fetch(url, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ taskId: task.id, updates: editedTask }) }); if (!response.ok) { throw new Error('Failed to update task'); } if (onSave) { onSave(editedTask); } } catch (err) { console.error('Error saving task:', err); setError('Failed to save task. Please try again.'); } finally { setIsSaving(false); } }; return ( <div className="task-detail-view edit-mode"> <div className="task-detail-header"> <h2> <span className="task-number">TASK {taskIndex + 1}</span> {task.name} <span className="edit-badge">EDITING</span> </h2> <button className="back-button" onClick={onBack}> ← Back to Tasks </button> </div> <div className="task-detail-content"> <div className="task-detail-section"> <div className="detail-row"> <span className="detail-label">Status:</span> <span className={`status-badge status-${task.status}`} style={{ backgroundColor: getStatusColor(task.status) }}> {task.status?.replace('_', ' ')} </span> </div> <div className="detail-row"> <span className="detail-label">ID:</span> <span className="detail-value monospace">{task.id}</span> </div> <div className="detail-row"> <span className="detail-label">Created:</span> <span className="detail-value">{formatDate(task.createdAt)}</span> </div> <div className="detail-row"> <span className="detail-label">Updated:</span> <span className="detail-value">{formatDate(task.updatedAt)}</span> </div> </div> <div className="task-detail-section"> <h3>{t('description')}</h3> <textarea className="edit-textarea" value={editedTask.description} onChange={(e) => handleInputChange('description', e.target.value)} placeholder="Enter task description..." rows={4} /> </div> <div className="task-detail-section"> <h3>Notes</h3> <textarea className="edit-textarea" value={editedTask.notes} onChange={(e) => handleInputChange('notes', e.target.value)} placeholder="Enter notes..." rows={3} /> </div> <div className="task-detail-section"> <h3>Implementation Guide</h3> <textarea className="edit-textarea" value={editedTask.implementationGuide} onChange={(e) => handleInputChange('implementationGuide', e.target.value)} placeholder="Enter implementation guide..." rows={6} /> </div> <div className="task-detail-section"> <h3>Verification Criteria</h3> <textarea className="edit-textarea" value={editedTask.verificationCriteria} onChange={(e) => handleInputChange('verificationCriteria', e.target.value)} placeholder="Enter verification criteria..." rows={4} /> </div> <div className="task-detail-section"> <h3>Assigned Agent</h3> <select className="agent-select" value={editedTask.agent} onChange={(e) => handleInputChange('agent', e.target.value)} > <option value="">No agent assigned</option> {availableAgents.map((agent) => { // Remove file extension from agent name for display and value const agentBaseName = agent.name.replace(/\.(md|yaml|yml)$/, ''); return ( <option key={agent.name} value={agentBaseName}> {agentBaseName} {agent.description ? `- ${agent.description.substring(0, 100)}${agent.description.length > 100 ? '...' : ''}` : ''} </option> ); })} </select> </div> {task.summary && ( <div className="task-detail-section"> <h3>Summary</h3> <div className="detail-content">{task.summary}</div> </div> )} {task.analysisResult && ( <div className="task-detail-section"> <h3>Analysis Result</h3> <div className="detail-content">{task.analysisResult}</div> </div> )} {task.dependencies && task.dependencies.length > 0 && ( <div className="task-detail-section"> <h3>Dependencies</h3> <div className="dependencies-list"> {task.dependencies.map((dep, idx) => { let depId, depName; if (typeof dep === 'string') { depId = dep; depName = null; } else if (dep && typeof dep === 'object') { depId = dep.taskId || dep.id; depName = dep.name; } else { return null; } const depTaskIndex = allTasks ? allTasks.findIndex(t => t.id === depId) : -1; const taskNumber = depTaskIndex >= 0 ? `TASK ${depTaskIndex + 1}` : 'TASK ?'; return ( <span key={idx} className="task-number dependency-badge" onClick={() => onNavigateToTask(depId)} title={`${depId}${depName ? ` - ${depName}` : ''}`} > {taskNumber} </span> ); })} </div> </div> )} {task.relatedFiles && task.relatedFiles.length > 0 && ( <div className="task-detail-section"> <h3>Related Files</h3> <div className="related-files-list"> {task.relatedFiles.map((file, idx) => ( <div key={idx} className="related-file-item"> <span className="file-type-icon">{getFileTypeIcon(file.type)}</span> <div className="file-info"> <div className="file-path monospace file-link" onClick={(e) => { const fullPath = projectRoot ? `${projectRoot}/${file.path}` : file.path; navigator.clipboard.writeText(fullPath); const element = e.currentTarget; element.classList.add('copied'); setTimeout(() => { element.classList.remove('copied'); }, 2000); }} title={projectRoot ? `Click to copy: ${projectRoot}/${file.path}` : `Click to copy: ${file.path}`} > {file.path} </div> {file.description && ( <div className="file-description">{file.description}</div> )} {(file.lineStart || file.lineEnd) && ( <div className="file-lines"> Lines: {file.lineStart || '?'} - {file.lineEnd || '?'} </div> )} </div> </div> ))} </div> </div> )} <div className="edit-actions"> {error && ( <div className="error-message">{error}</div> )} <button className="save-button" onClick={handleSave} disabled={isSaving} > {isSaving ? 'Saving...' : 'Save Changes'} </button> <button className="cancel-button" onClick={onBack} disabled={isSaving} > Cancel </button> </div> </div> </div> ); } export default TaskEditView;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cjo4m06/mcp-shrimp-task-manager'

If you have feedback or need assistance with the MCP directory API, please join our Discord server