From a419374cb28b92ebba1693854201e0950b2d7abb Mon Sep 17 00:00:00 2001 From: MayaTheShy Date: Thu, 19 Feb 2026 22:52:54 -0500 Subject: [PATCH] feat: Add TaskPanel component for managing tasks with create, update, and delete functionalities --- client/src/components/TaskPanel.jsx | 428 ++++++++++++++++++++++++++++ 1 file changed, 428 insertions(+) create mode 100644 client/src/components/TaskPanel.jsx diff --git a/client/src/components/TaskPanel.jsx b/client/src/components/TaskPanel.jsx new file mode 100644 index 0000000..1fb4349 --- /dev/null +++ b/client/src/components/TaskPanel.jsx @@ -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 ( +
+
+

๐Ÿ“‹ Task Queue

+ +
+ + {message && ( +
+ {message.text} +
+ )} + + {/* Create Task Form */} + {showCreateForm && ( +
+

Create New Task

+
+
+ + + +
+ + + +
+

Coordinates (optional):

+
+
+ Start: + setParameters({...parameters, x1: e.target.value})} + /> + setParameters({...parameters, y1: e.target.value})} + /> + setParameters({...parameters, z1: e.target.value})} + /> +
+
+ End: + setParameters({...parameters, x2: e.target.value})} + /> + setParameters({...parameters, y2: e.target.value})} + /> + setParameters({...parameters, z2: e.target.value})} + /> +
+
+
+ + +
+
+ )} + + {/* Filter Tabs */} +
+ + + + + +
+ + {/* Tasks List */} +
+ {loading &&
Loading tasks...
} + + {!loading && tasks.length === 0 && ( +
+
๐Ÿ“‹
+
No Tasks Found
+
+ {filter === 'all' + ? 'Create a task to get started!' + : `No ${filter.replace('_', ' ')} tasks`} +
+
+ )} + + {tasks.map(task => { + const priorityInfo = getPriorityLabel(task.priority); + const turtle = turtles.find(t => t.turtleID === task.assignedTurtleId); + + return ( +
+
+
+ {getTaskIcon(task.taskType)} + + {taskTypes.find(t => t.value === task.taskType)?.label || task.taskType} + +
+
+ + {priorityInfo.label} + + + {task.status.replace('_', ' ')} + +
+
+ + {task.assignedTurtleId && ( +
+ ๐Ÿข {turtle?.name || `Turtle ${task.assignedTurtleId}`} +
+ )} + + {task.parameters && Object.keys(task.parameters).length > 0 && ( +
+ Area: ({task.parameters.x1}, {task.parameters.y1}, {task.parameters.z1}) โ†’ + ({task.parameters.x2}, {task.parameters.y2}, {task.parameters.z2}) +
+ )} + + {task.result && ( +
+ Result: {task.result} +
+ )} + +
+ {task.status === 'pending' && ( + <> + + + + )} + {task.status === 'in_progress' && ( + <> + + + + )} + {(task.status === 'completed' || task.status === 'failed') && ( + + )} +
+ +
+ Created: {new Date(task.createdAt).toLocaleString()} +
+
+ ); + })} +
+
+ ); +}; + +export default TaskPanel;