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 ( +