Implement task deletion functionality in TaskCard component
- Added context menu for task deletion with confirmation modal. - Integrated localization for delete task prompts in multiple languages. - Updated TaskCard to handle task deletion logic, including dispatching actions to update the state and emit socket events for task progress.
This commit is contained in:
@@ -10,6 +10,11 @@
|
|||||||
"deleteConfirmationOk": "Po",
|
"deleteConfirmationOk": "Po",
|
||||||
"deleteConfirmationCancel": "Anulo",
|
"deleteConfirmationCancel": "Anulo",
|
||||||
|
|
||||||
|
"deleteTaskTitle": "Fshi Detyrën",
|
||||||
|
"deleteTaskContent": "Jeni i sigurt që doni të fshini këtë detyrë? Kjo veprim nuk mund të zhbëhet.",
|
||||||
|
"deleteTaskConfirm": "Fshi",
|
||||||
|
"deleteTaskCancel": "Anulo",
|
||||||
|
|
||||||
"dueDate": "Data e përfundimit",
|
"dueDate": "Data e përfundimit",
|
||||||
"cancel": "Anulo",
|
"cancel": "Anulo",
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,11 @@
|
|||||||
"deleteConfirmationOk": "Ja",
|
"deleteConfirmationOk": "Ja",
|
||||||
"deleteConfirmationCancel": "Abbrechen",
|
"deleteConfirmationCancel": "Abbrechen",
|
||||||
|
|
||||||
|
"deleteTaskTitle": "Aufgabe löschen",
|
||||||
|
"deleteTaskContent": "Sind Sie sicher, dass Sie diese Aufgabe löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||||
|
"deleteTaskConfirm": "Löschen",
|
||||||
|
"deleteTaskCancel": "Abbrechen",
|
||||||
|
|
||||||
"dueDate": "Fälligkeitsdatum",
|
"dueDate": "Fälligkeitsdatum",
|
||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,11 @@
|
|||||||
"deleteConfirmationOk": "Yes",
|
"deleteConfirmationOk": "Yes",
|
||||||
"deleteConfirmationCancel": "Cancel",
|
"deleteConfirmationCancel": "Cancel",
|
||||||
|
|
||||||
|
"deleteTaskTitle": "Delete Task",
|
||||||
|
"deleteTaskContent": "Are you sure you want to delete this task? This action cannot be undone.",
|
||||||
|
"deleteTaskConfirm": "Delete",
|
||||||
|
"deleteTaskCancel": "Cancel",
|
||||||
|
|
||||||
"dueDate": "Due date",
|
"dueDate": "Due date",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,11 @@
|
|||||||
"deleteConfirmationOk": "Sí",
|
"deleteConfirmationOk": "Sí",
|
||||||
"deleteConfirmationCancel": "Cancelar",
|
"deleteConfirmationCancel": "Cancelar",
|
||||||
|
|
||||||
|
"deleteTaskTitle": "Eliminar tarea",
|
||||||
|
"deleteTaskContent": "¿Estás seguro de que deseas eliminar esta tarea? Esta acción no se puede deshacer.",
|
||||||
|
"deleteTaskConfirm": "Eliminar",
|
||||||
|
"deleteTaskCancel": "Cancelar",
|
||||||
|
|
||||||
"dueDate": "Fecha de vencimiento",
|
"dueDate": "Fecha de vencimiento",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,11 @@
|
|||||||
"deleteConfirmationOk": "Sim",
|
"deleteConfirmationOk": "Sim",
|
||||||
"deleteConfirmationCancel": "Cancelar",
|
"deleteConfirmationCancel": "Cancelar",
|
||||||
|
|
||||||
|
"deleteTaskTitle": "Excluir Tarefa",
|
||||||
|
"deleteTaskContent": "Tem certeza de que deseja excluir esta tarefa? Esta ação não pode ser desfeita.",
|
||||||
|
"deleteTaskConfirm": "Excluir",
|
||||||
|
"deleteTaskCancel": "Cancelar",
|
||||||
|
|
||||||
"dueDate": "Data de vencimento",
|
"dueDate": "Data de vencimento",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
|
|
||||||
|
|||||||
@@ -15,5 +15,9 @@
|
|||||||
"assignToMe": "分配给我",
|
"assignToMe": "分配给我",
|
||||||
"archive": "归档",
|
"archive": "归档",
|
||||||
"newTaskNamePlaceholder": "写一个任务名称",
|
"newTaskNamePlaceholder": "写一个任务名称",
|
||||||
"newSubtaskNamePlaceholder": "写一个子任务名称"
|
"newSubtaskNamePlaceholder": "写一个子任务名称",
|
||||||
|
"deleteTaskTitle": "删除任务",
|
||||||
|
"deleteTaskContent": "您确定要删除此任务吗?此操作无法撤销。",
|
||||||
|
"deleteTaskConfirm": "删除",
|
||||||
|
"deleteTaskCancel": "取消"
|
||||||
}
|
}
|
||||||
@@ -14,8 +14,11 @@ import { useSocket } from '@/socket/socketContext';
|
|||||||
import { SocketEvents } from '@/shared/socket-events';
|
import { SocketEvents } from '@/shared/socket-events';
|
||||||
import { getUserSession } from '@/utils/session-helper';
|
import { getUserSession } from '@/utils/session-helper';
|
||||||
import { themeWiseColor } from '@/utils/themeWiseColor';
|
import { themeWiseColor } from '@/utils/themeWiseColor';
|
||||||
import { toggleTaskExpansion, fetchBoardSubTasks } from '@/features/enhanced-kanban/enhanced-kanban.slice';
|
import { toggleTaskExpansion, fetchBoardSubTasks, deleteTask as deleteKanbanTask, updateEnhancedKanbanSubtask } from '@/features/enhanced-kanban/enhanced-kanban.slice';
|
||||||
import TaskProgressCircle from './TaskProgressCircle';
|
import TaskProgressCircle from './TaskProgressCircle';
|
||||||
|
import { Button, Modal } from 'antd';
|
||||||
|
import { DeleteOutlined } from '@ant-design/icons';
|
||||||
|
import { tasksApiService } from '@/api/tasks/tasks.api.service';
|
||||||
|
|
||||||
// Simple Portal component
|
// Simple Portal component
|
||||||
const Portal: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
const Portal: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
@@ -70,6 +73,9 @@ const TaskCard: React.FC<TaskCardProps> = memo(({
|
|||||||
const d = selectedDate || new Date();
|
const d = selectedDate || new Date();
|
||||||
return new Date(d.getFullYear(), d.getMonth(), 1);
|
return new Date(d.getFullYear(), d.getMonth(), 1);
|
||||||
});
|
});
|
||||||
|
const [contextMenu, setContextMenu] = useState<{ visible: boolean; x: number; y: number }>({ visible: false, x: 0, y: 0 });
|
||||||
|
const contextMenuRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [selectedTask, setSelectedTask] = useState<IProjectTask | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedDate(task.end_date ? new Date(task.end_date) : null);
|
setSelectedDate(task.end_date ? new Date(task.end_date) : null);
|
||||||
@@ -102,6 +108,21 @@ const TaskCard: React.FC<TaskCardProps> = memo(({
|
|||||||
}
|
}
|
||||||
}, [showDatePicker]);
|
}, [showDatePicker]);
|
||||||
|
|
||||||
|
// Hide context menu on click elsewhere
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClick = (e: MouseEvent) => {
|
||||||
|
if (contextMenuRef.current && !contextMenuRef.current.contains(e.target as Node)) {
|
||||||
|
setContextMenu({ ...contextMenu, visible: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (contextMenu.visible) {
|
||||||
|
document.addEventListener('mousedown', handleClick);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClick);
|
||||||
|
};
|
||||||
|
}, [contextMenu]);
|
||||||
|
|
||||||
const handleCardClick = useCallback((e: React.MouseEvent, id: string) => {
|
const handleCardClick = useCallback((e: React.MouseEvent, id: string) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
dispatch(setSelectedTaskId(id));
|
dispatch(setSelectedTaskId(id));
|
||||||
@@ -178,6 +199,48 @@ const TaskCard: React.FC<TaskCardProps> = memo(({
|
|||||||
handleSubTaskExpand();
|
handleSubTaskExpand();
|
||||||
}, [handleSubTaskExpand]);
|
}, [handleSubTaskExpand]);
|
||||||
|
|
||||||
|
// Delete logic (similar to task-drawer-header)
|
||||||
|
const handleDeleteTask = async (task: IProjectTask | null) => {
|
||||||
|
if (!task || !task.id) return;
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('deleteTaskTitle'),
|
||||||
|
content: t('deleteTaskContent'),
|
||||||
|
okText: t('deleteTaskConfirm'),
|
||||||
|
okType: 'danger',
|
||||||
|
cancelText: t('deleteTaskCancel'),
|
||||||
|
centered: true,
|
||||||
|
onOk: async () => {
|
||||||
|
if (!task.id) return;
|
||||||
|
const res = await tasksApiService.deleteTask(task.id);
|
||||||
|
if (res.done) {
|
||||||
|
dispatch(setSelectedTaskId(null));
|
||||||
|
if (task.is_sub_task) {
|
||||||
|
dispatch(updateEnhancedKanbanSubtask({
|
||||||
|
sectionId: '',
|
||||||
|
subtask: { id: task.id , parent_task_id: task.parent_task_id || '', manual_progress: false },
|
||||||
|
mode: 'delete',
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
dispatch(deleteKanbanTask(task.id));
|
||||||
|
}
|
||||||
|
dispatch(setShowTaskDrawer(false));
|
||||||
|
if (task.parent_task_id) {
|
||||||
|
socket?.emit(
|
||||||
|
SocketEvents.GET_TASK_PROGRESS.toString(),
|
||||||
|
task.parent_task_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setContextMenu({ visible: false, x: 0, y: 0 });
|
||||||
|
setSelectedTask(null);
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
setContextMenu({ visible: false, x: 0, y: 0 });
|
||||||
|
setSelectedTask(null);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Calendar rendering helpers
|
// Calendar rendering helpers
|
||||||
const year = calendarMonth.getFullYear();
|
const year = calendarMonth.getFullYear();
|
||||||
const month = calendarMonth.getMonth();
|
const month = calendarMonth.getMonth();
|
||||||
@@ -202,7 +265,37 @@ const TaskCard: React.FC<TaskCardProps> = memo(({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="enhanced-kanban-task-card" style={{ background, color, display: 'block', position: 'relative' }} >
|
{/* Context menu for delete */}
|
||||||
|
{contextMenu.visible && (
|
||||||
|
<div
|
||||||
|
ref={contextMenuRef}
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: contextMenu.y,
|
||||||
|
left: contextMenu.x,
|
||||||
|
zIndex: 9999,
|
||||||
|
background: themeMode === 'dark' ? '#23272f' : '#fff',
|
||||||
|
borderRadius: 8,
|
||||||
|
boxShadow: '0 2px 8px rgba(0,0,0,0.12)',
|
||||||
|
padding: 0,
|
||||||
|
minWidth: 120,
|
||||||
|
transition: 'translateY(0)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<DeleteOutlined style={{ color: '#ef4444', fontSize: 16 }} />}
|
||||||
|
style={{ color: '#ef4444', width: '100%', textAlign: 'left', padding: '8px 16px', fontWeight: 500 }}
|
||||||
|
onClick={() => handleDeleteTask(selectedTask || null)}
|
||||||
|
>
|
||||||
|
{t('delete')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className="enhanced-kanban-task-card"
|
||||||
|
style={{ background, color, display: 'block', position: 'relative' }}
|
||||||
|
>
|
||||||
{/* Progress circle at top right */}
|
{/* Progress circle at top right */}
|
||||||
<div style={{ position: 'absolute', top: 6, right: 6, zIndex: 2 }}>
|
<div style={{ position: 'absolute', top: 6, right: 6, zIndex: 2 }}>
|
||||||
<TaskProgressCircle task={task} size={20} />
|
<TaskProgressCircle task={task} size={20} />
|
||||||
@@ -221,6 +314,11 @@ const TaskCard: React.FC<TaskCardProps> = memo(({
|
|||||||
onDrop={e => onTaskDrop(e, groupId, idx)}
|
onDrop={e => onTaskDrop(e, groupId, idx)}
|
||||||
onDragEnd={onDragEnd} // <-- add this
|
onDragEnd={onDragEnd} // <-- add this
|
||||||
onClick={e => handleCardClick(e, task.id!)}
|
onClick={e => handleCardClick(e, task.id!)}
|
||||||
|
onContextMenu={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
setContextMenu({ visible: true, x: e.clientX, y: e.clientY });
|
||||||
|
setSelectedTask(task);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="task-content">
|
<div className="task-content">
|
||||||
<div className="task_labels" style={{ display: 'flex', gap: 4, marginBottom: 4 }}>
|
<div className="task_labels" style={{ display: 'flex', gap: 4, marginBottom: 4 }}>
|
||||||
@@ -447,7 +545,14 @@ const TaskCard: React.FC<TaskCardProps> = memo(({
|
|||||||
{!task.sub_tasks_loading && Array.isArray(task.sub_tasks) && task.sub_tasks.length > 0 && (
|
{!task.sub_tasks_loading && Array.isArray(task.sub_tasks) && task.sub_tasks.length > 0 && (
|
||||||
<ul className="space-y-1">
|
<ul className="space-y-1">
|
||||||
{task.sub_tasks.map(sub => (
|
{task.sub_tasks.map(sub => (
|
||||||
<li key={sub.id} onClick={e => handleCardClick(e, sub.id!)} className="flex items-center gap-2 px-2 py-1 rounded hover:bg-gray-50 dark:hover:bg-gray-800">
|
<li key={sub.id}
|
||||||
|
onClick={e => handleCardClick(e, sub.id!)}
|
||||||
|
className="flex items-center gap-2 px-2 py-1 rounded hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||||
|
onContextMenu={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
setContextMenu({ visible: true, x: e.clientX, y: e.clientY });
|
||||||
|
setSelectedTask(sub);
|
||||||
|
}}>
|
||||||
{sub.priority_color || sub.priority_color_dark ? (
|
{sub.priority_color || sub.priority_color_dark ? (
|
||||||
<span
|
<span
|
||||||
className="w-2 h-2 rounded-full inline-block"
|
className="w-2 h-2 rounded-full inline-block"
|
||||||
|
|||||||
Reference in New Issue
Block a user