diff --git a/client/src/components/common/ConfirmDialog.jsx b/client/src/components/common/ConfirmDialog.jsx new file mode 100644 index 0000000..7adbbfc --- /dev/null +++ b/client/src/components/common/ConfirmDialog.jsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { AlertTriangle, X } from 'lucide-react'; + +const ConfirmDialog = ({ isOpen, onClose, onConfirm, title, message, confirmText = 'Confirm', cancelText = 'Cancel', type = 'warning' }) => { + if (!isOpen) return null; + + const typeStyles = { + warning: { + icon: AlertTriangle, + iconColor: 'text-yellow-600', + confirmButton: 'bg-yellow-600 hover:bg-yellow-700 text-white' + }, + danger: { + icon: AlertTriangle, + iconColor: 'text-red-600', + confirmButton: 'bg-red-600 hover:bg-red-700 text-white' + } + }; + + const config = typeStyles[type] || typeStyles.warning; + const Icon = config.icon; + + return ( +
+
+ {/* Background overlay */} +
+ + {/* Modal */} +
+ {/* Close button */} + + + {/* Content */} +
+
+ +
+
+

+ {title} +

+

+ {message} +

+ + {/* Actions */} +
+ + +
+
+
+
+
+
+ ); +}; + +export default ConfirmDialog; \ No newline at end of file diff --git a/client/src/pages/Tasks.jsx b/client/src/pages/Tasks.jsx index e0fe59b..d72d3dd 100644 --- a/client/src/pages/Tasks.jsx +++ b/client/src/pages/Tasks.jsx @@ -5,6 +5,8 @@ import { projectService } from '../services/projectService'; import Layout from '../components/layout/Layout'; import { LoadingSpinner, Button, Select, Input } from '../components/common'; import TaskModal from '../components/tasks/TaskModal'; +import ConfirmDialog from '../components/common/ConfirmDialog'; +import { useToast } from '../hooks/useToast'; import { Plus, Search, @@ -33,7 +35,10 @@ const Tasks = () => { const [openMenuId, setOpenMenuId] = useState(null); const [showTaskModal, setShowTaskModal] = useState(false); const [selectedTask, setSelectedTask] = useState(null); + const [showConfirmDialog, setShowConfirmDialog] = useState(false); + const [taskToDelete, setTaskToDelete] = useState(null); const menuRef = useRef(null); + const { toast } = useToast(); // Set search query from URL parameter useEffect(() => { @@ -47,6 +52,8 @@ const Tasks = () => { fetchData(); }, []); + + useEffect(() => { filterTasks(); }, [tasks, searchQuery, statusFilter, priorityFilter, projectFilter]); @@ -79,7 +86,8 @@ const Tasks = () => { setProjects(projectsResponse.data.projects || []); setTasks(recentTasksResponse.data || []); } catch (error) { - console.error('Error fetching data:', error); + const errorMessage = error.response?.data?.message || 'Failed to load tasks'; + toast.error(errorMessage); } finally { setLoading(false); } @@ -114,21 +122,34 @@ const Tasks = () => { setFilteredTasks(filtered); }; - const handleDeleteTask = async (taskId) => { - if (!window.confirm('Are you sure you want to delete this task?')) { - return; - } + const handleDeleteTask = (taskId) => { + const task = tasks.find(t => t._id === taskId); + setTaskToDelete(task); + setShowConfirmDialog(true); + setOpenMenuId(null); + }; + + const confirmDeleteTask = async () => { + if (!taskToDelete) return; try { - await taskService.deleteTask(taskId); - setTasks(tasks.filter(task => task._id !== taskId)); - setOpenMenuId(null); + await taskService.deleteTask(taskToDelete._id); + setTasks(tasks.filter(task => task._id !== taskToDelete._id)); + toast.success(`"${taskToDelete.title}" deleted successfully`); } catch (error) { - console.error('Error deleting task:', error); - alert('Failed to delete task. Please try again.'); + const errorMessage = error.response?.data?.message || 'Failed to delete task'; + toast.error(errorMessage); + } finally { + setShowConfirmDialog(false); + setTaskToDelete(null); } }; + const cancelDeleteTask = () => { + setShowConfirmDialog(false); + setTaskToDelete(null); + }; + const handleEditTask = (taskId) => { const task = tasks.find(t => t._id === taskId); setSelectedTask(task); @@ -155,8 +176,7 @@ const Tasks = () => { setShowTaskModal(false); fetchData(); // Refresh to get updated task with populated fields } catch (error) { - console.error('Error saving task:', error); - throw error; + throw error; // Let TaskModal handle the error display } }; @@ -457,52 +477,23 @@ const Tasks = () => { -
+
+ - - {openMenuId === task._id && (() => { - const button = document.getElementById(`menu-button-${task._id}`); - const rect = button?.getBoundingClientRect(); - return ( -
- - -
- ); - })()}
@@ -515,52 +506,32 @@ const Tasks = () => {
{filteredTasks.map((task) => (
-
+
{getStatusIcon(task.status)}

{task.title}

-
+
+ - - {openMenuId === task._id && ( -
- - -
- )}
-
- - {task.description && ( +
{task.description && (

{task.description}

@@ -633,6 +604,17 @@ const Tasks = () => { projectId={projectFilter !== 'all' ? projectFilter : null} onSave={handleSaveTask} /> + + {/* Delete Confirmation Dialog */} + ); };