feat: Add TaskPanel component for managing tasks with create, update, and delete functionalities

This commit is contained in:
MayaTheShy
2026-02-19 22:52:54 -05:00
parent f402f3665c
commit a419374cb2

View File

@@ -0,0 +1,428 @@
import { useState, useEffect } from 'react';
import './TaskPanel.css';
const TaskPanel = ({ turtles, apiUrl }) => {
const [tasks, setTasks] = useState([]);
const [filter, setFilter] = useState('all'); // all, pending, in_progress, completed, failed
const [showCreateForm, setShowCreateForm] = useState(false);
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState(null);
// Form state
const [taskType, setTaskType] = useState('mine_area');
const [priority, setPriority] = useState(5);
const [assignedTurtleId, setAssignedTurtleId] = useState('');
const [parameters, setParameters] = useState({
x1: '', y1: '', z1: '',
x2: '', y2: '', z2: ''
});
const taskTypes = [
{ value: 'mine_area', label: '⛏️ Mine Area', icon: '⛏️' },
{ value: 'explore', label: '🔍 Explore', icon: '🔍' },
{ value: 'gather', label: '📦 Gather Resources', icon: '📦' },
{ value: 'build', label: '🏗️ Build Structure', icon: '🏗️' },
{ value: 'transport', label: '🚚 Transport Items', icon: '🚚' },
{ value: 'clear_area', label: '🧹 Clear Area', icon: '🧹' },
];
useEffect(() => {
loadTasks();
// Refresh tasks every 10 seconds
const interval = setInterval(loadTasks, 10000);
return () => clearInterval(interval);
}, [filter]);
const loadTasks = async () => {
setLoading(true);
try {
const url = filter === 'all'
? `${apiUrl}/api/tasks`
: `${apiUrl}/api/tasks?status=${filter}`;
const response = await fetch(url);
if (response.ok) {
const data = await response.json();
setTasks(data);
}
} catch (error) {
console.error('Failed to load tasks:', error);
} finally {
setLoading(false);
}
};
const createTask = async (e) => {
e.preventDefault();
// Validate coordinates
const coords = ['x1', 'y1', 'z1', 'x2', 'y2', 'z2'];
const hasCoords = coords.some(key => parameters[key] !== '');
if (hasCoords && !coords.every(key => parameters[key] !== '')) {
showMessage('Please fill all coordinates or leave them empty', 'error');
return;
}
const taskData = {
taskType,
priority: parseInt(priority),
assignedTurtleId: assignedTurtleId ? parseInt(assignedTurtleId) : null,
parameters: hasCoords ? {
x1: parseInt(parameters.x1),
y1: parseInt(parameters.y1),
z1: parseInt(parameters.z1),
x2: parseInt(parameters.x2),
y2: parseInt(parameters.y2),
z2: parseInt(parameters.z2)
} : {}
};
try {
const response = await fetch(`${apiUrl}/api/tasks`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(taskData)
});
if (response.ok) {
showMessage('Task created successfully!', 'success');
setShowCreateForm(false);
resetForm();
loadTasks();
} else {
showMessage('Failed to create task', 'error');
}
} catch (error) {
showMessage('Error creating task', 'error');
}
};
const updateTaskStatus = async (taskId, status, result = null) => {
try {
const response = await fetch(`${apiUrl}/api/tasks/${taskId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status, result })
});
if (response.ok) {
showMessage('Task updated', 'success');
loadTasks();
} else {
showMessage('Failed to update task', 'error');
}
} catch (error) {
showMessage('Error updating task', 'error');
}
};
const deleteTask = async (taskId) => {
if (!confirm('Are you sure you want to delete this task?')) return;
try {
const response = await fetch(`${apiUrl}/api/tasks/${taskId}`, {
method: 'DELETE'
});
if (response.ok) {
showMessage('Task deleted', 'success');
loadTasks();
} else {
showMessage('Failed to delete task', 'error');
}
} catch (error) {
showMessage('Error deleting task', 'error');
}
};
const resetForm = () => {
setTaskType('mine_area');
setPriority(5);
setAssignedTurtleId('');
setParameters({ x1: '', y1: '', z1: '', x2: '', y2: '', z2: '' });
};
const showMessage = (text, type) => {
setMessage({ text, type });
setTimeout(() => setMessage(null), 3000);
};
const getTaskIcon = (type) => {
const taskType = taskTypes.find(t => t.value === type);
return taskType ? taskType.icon : '📋';
};
const getStatusColor = (status) => {
switch (status) {
case 'pending': return '#94a3b8';
case 'in_progress': return '#3b82f6';
case 'completed': return '#10b981';
case 'failed': return '#ef4444';
default: return '#94a3b8';
}
};
const getPriorityLabel = (priority) => {
if (priority >= 8) return { label: 'Critical', color: '#ef4444' };
if (priority >= 6) return { label: 'High', color: '#f59e0b' };
if (priority >= 4) return { label: 'Medium', color: '#3b82f6' };
return { label: 'Low', color: '#94a3b8' };
};
return (
<div className="task-panel">
<div className="task-header">
<h2>📋 Task Queue</h2>
<button
className="create-task-btn"
onClick={() => setShowCreateForm(!showCreateForm)}
>
{showCreateForm ? '✕ Cancel' : ' New Task'}
</button>
</div>
{message && (
<div className={`message ${message.type}`}>
{message.text}
</div>
)}
{/* Create Task Form */}
{showCreateForm && (
<div className="create-task-form">
<h3>Create New Task</h3>
<form onSubmit={createTask}>
<div className="form-row">
<label>
Task Type:
<select value={taskType} onChange={(e) => setTaskType(e.target.value)}>
{taskTypes.map(type => (
<option key={type.value} value={type.value}>
{type.label}
</option>
))}
</select>
</label>
<label>
Priority (1-10):
<input
type="range"
min="1"
max="10"
value={priority}
onChange={(e) => setPriority(e.target.value)}
/>
<span className="priority-value" style={{ color: getPriorityLabel(priority).color }}>
{priority} - {getPriorityLabel(priority).label}
</span>
</label>
</div>
<label>
Assign to Turtle (optional):
<select value={assignedTurtleId} onChange={(e) => setAssignedTurtleId(e.target.value)}>
<option value="">Any available turtle</option>
{turtles.map(turtle => (
<option key={turtle.turtleID} value={turtle.turtleID}>
🐢 {turtle.name || `Turtle ${turtle.turtleID}`}
</option>
))}
</select>
</label>
<div className="coordinates-section">
<h4>Coordinates (optional):</h4>
<div className="coordinates-grid">
<div className="coord-group">
<span>Start:</span>
<input
type="number"
placeholder="X1"
value={parameters.x1}
onChange={(e) => setParameters({...parameters, x1: e.target.value})}
/>
<input
type="number"
placeholder="Y1"
value={parameters.y1}
onChange={(e) => setParameters({...parameters, y1: e.target.value})}
/>
<input
type="number"
placeholder="Z1"
value={parameters.z1}
onChange={(e) => setParameters({...parameters, z1: e.target.value})}
/>
</div>
<div className="coord-group">
<span>End:</span>
<input
type="number"
placeholder="X2"
value={parameters.x2}
onChange={(e) => setParameters({...parameters, x2: e.target.value})}
/>
<input
type="number"
placeholder="Y2"
value={parameters.y2}
onChange={(e) => setParameters({...parameters, y2: e.target.value})}
/>
<input
type="number"
placeholder="Z2"
value={parameters.z2}
onChange={(e) => setParameters({...parameters, z2: e.target.value})}
/>
</div>
</div>
</div>
<button type="submit" className="submit-btn">
Create Task
</button>
</form>
</div>
)}
{/* Filter Tabs */}
<div className="task-filters">
<button
className={filter === 'all' ? 'active' : ''}
onClick={() => setFilter('all')}
>
All ({tasks.length})
</button>
<button
className={filter === 'pending' ? 'active' : ''}
onClick={() => setFilter('pending')}
>
Pending
</button>
<button
className={filter === 'in_progress' ? 'active' : ''}
onClick={() => setFilter('in_progress')}
>
In Progress
</button>
<button
className={filter === 'completed' ? 'active' : ''}
onClick={() => setFilter('completed')}
>
Completed
</button>
<button
className={filter === 'failed' ? 'active' : ''}
onClick={() => setFilter('failed')}
>
Failed
</button>
</div>
{/* Tasks List */}
<div className="tasks-list">
{loading && <div className="loading">Loading tasks...</div>}
{!loading && tasks.length === 0 && (
<div className="empty-state">
<div className="empty-icon">📋</div>
<div className="empty-title">No Tasks Found</div>
<div className="empty-text">
{filter === 'all'
? 'Create a task to get started!'
: `No ${filter.replace('_', ' ')} tasks`}
</div>
</div>
)}
{tasks.map(task => {
const priorityInfo = getPriorityLabel(task.priority);
const turtle = turtles.find(t => t.turtleID === task.assignedTurtleId);
return (
<div key={task.taskId} className="task-card">
<div className="task-card-header">
<div className="task-title">
<span className="task-icon">{getTaskIcon(task.taskType)}</span>
<span className="task-type">
{taskTypes.find(t => t.value === task.taskType)?.label || task.taskType}
</span>
</div>
<div className="task-badges">
<span
className="priority-badge"
style={{ backgroundColor: priorityInfo.color }}
>
{priorityInfo.label}
</span>
<span
className="status-badge"
style={{ backgroundColor: getStatusColor(task.status) }}
>
{task.status.replace('_', ' ')}
</span>
</div>
</div>
{task.assignedTurtleId && (
<div className="task-assignment">
🐢 {turtle?.name || `Turtle ${task.assignedTurtleId}`}
</div>
)}
{task.parameters && Object.keys(task.parameters).length > 0 && (
<div className="task-parameters">
<strong>Area:</strong> ({task.parameters.x1}, {task.parameters.y1}, {task.parameters.z1})
({task.parameters.x2}, {task.parameters.y2}, {task.parameters.z2})
</div>
)}
{task.result && (
<div className="task-result">
<strong>Result:</strong> {task.result}
</div>
)}
<div className="task-actions">
{task.status === 'pending' && (
<>
<button onClick={() => updateTaskStatus(task.taskId, 'in_progress')}>
Start
</button>
<button onClick={() => deleteTask(task.taskId)}>
🗑 Delete
</button>
</>
)}
{task.status === 'in_progress' && (
<>
<button onClick={() => updateTaskStatus(task.taskId, 'completed', 'Task completed')}>
Complete
</button>
<button onClick={() => updateTaskStatus(task.taskId, 'failed', 'Task failed')}>
Fail
</button>
</>
)}
{(task.status === 'completed' || task.status === 'failed') && (
<button onClick={() => deleteTask(task.taskId)}>
🗑 Delete
</button>
)}
</div>
<div className="task-timestamp">
Created: {new Date(task.createdAt).toLocaleString()}
</div>
</div>
);
})}
</div>
</div>
);
};
export default TaskPanel;