feat(enhanced-kanban): add task creation functionality to Kanban groups
- Introduced EnhancedKanbanCreateTaskCard component for adding tasks directly within Kanban groups. - Implemented conditional rendering of task creation buttons based on user roles (owner/admin or project manager). - Enhanced the KanbanGroup component to support task creation at both the top and bottom of the task list, improving user experience.
This commit is contained in:
@@ -12,6 +12,7 @@ import { reorderGroups, reorderEnhancedKanbanGroups, reorderTasks, reorderEnhanc
|
|||||||
import { fetchStatusesCategories } from '@/features/taskAttributes/taskStatusSlice';
|
import { fetchStatusesCategories } from '@/features/taskAttributes/taskStatusSlice';
|
||||||
import { useAppSelector } from '@/hooks/useAppSelector';
|
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||||
import KanbanGroup from './KanbanGroup';
|
import KanbanGroup from './KanbanGroup';
|
||||||
|
import EnhancedKanbanCreateSection from '../EnhancedKanbanCreateSection';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -222,6 +223,7 @@ const EnhancedKanbanBoardNativeDnD: React.FC<{ projectId: string }> = ({ project
|
|||||||
hoveredGroupId={hoveredGroupId}
|
hoveredGroupId={hoveredGroupId}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
<EnhancedKanbanCreateSection />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,86 +1,169 @@
|
|||||||
import React, { memo, useMemo } from 'react';
|
import React, { memo, useMemo, useState } from 'react';
|
||||||
import { useAppSelector } from '@/hooks/useAppSelector';
|
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||||
import { ITaskListGroup } from '@/types/tasks/taskList.types';
|
import { ITaskListGroup } from '@/types/tasks/taskList.types';
|
||||||
import TaskCard from './TaskCard';
|
import TaskCard from './TaskCard';
|
||||||
|
import { themeWiseColor } from '@/utils/themeWiseColor';
|
||||||
|
import EnhancedKanbanCreateTaskCard from '../EnhancedKanbanCreateTaskCard';
|
||||||
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
|
import Button from 'antd/es/button';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useAuthService } from '@/hooks/useAuth';
|
||||||
|
import useIsProjectManager from '@/hooks/useIsProjectManager';
|
||||||
|
|
||||||
interface KanbanGroupProps {
|
interface KanbanGroupProps {
|
||||||
group: ITaskListGroup;
|
group: ITaskListGroup;
|
||||||
onGroupDragStart: (e: React.DragEvent, groupId: string) => void;
|
onGroupDragStart: (e: React.DragEvent, groupId: string) => void;
|
||||||
onGroupDragOver: (e: React.DragEvent) => void;
|
onGroupDragOver: (e: React.DragEvent) => void;
|
||||||
onGroupDrop: (e: React.DragEvent, groupId: string) => void;
|
onGroupDrop: (e: React.DragEvent, groupId: string) => void;
|
||||||
onTaskDragStart: (e: React.DragEvent, taskId: string, groupId: string) => void;
|
onTaskDragStart: (e: React.DragEvent, taskId: string, groupId: string) => void;
|
||||||
onTaskDragOver: (e: React.DragEvent, groupId: string, taskIdx: number) => void;
|
onTaskDragOver: (e: React.DragEvent, groupId: string, taskIdx: number) => void;
|
||||||
onTaskDrop: (e: React.DragEvent, groupId: string, taskIdx: number) => void;
|
onTaskDrop: (e: React.DragEvent, groupId: string, taskIdx: number) => void;
|
||||||
hoveredTaskIdx: number | null;
|
hoveredTaskIdx: number | null;
|
||||||
hoveredGroupId: string | null;
|
hoveredGroupId: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const KanbanGroup: React.FC<KanbanGroupProps> = memo(({
|
const KanbanGroup: React.FC<KanbanGroupProps> = memo(({
|
||||||
group,
|
group,
|
||||||
onGroupDragStart,
|
onGroupDragStart,
|
||||||
onGroupDragOver,
|
onGroupDragOver,
|
||||||
onGroupDrop,
|
onGroupDrop,
|
||||||
onTaskDragStart,
|
onTaskDragStart,
|
||||||
onTaskDragOver,
|
onTaskDragOver,
|
||||||
onTaskDrop,
|
onTaskDrop,
|
||||||
hoveredTaskIdx,
|
hoveredTaskIdx,
|
||||||
hoveredGroupId
|
hoveredGroupId
|
||||||
}) => {
|
}) => {
|
||||||
const themeMode = useAppSelector(state => state.themeReducer.mode);
|
const themeMode = useAppSelector(state => state.themeReducer.mode);
|
||||||
|
const { t } = useTranslation('kanban-board');
|
||||||
|
const isOwnerOrAdmin = useAuthService().isOwnerOrAdmin();
|
||||||
|
const isProjectManager = useIsProjectManager();
|
||||||
|
const [showNewCardTop, setShowNewCardTop] = useState(false);
|
||||||
|
const [showNewCardBottom, setShowNewCardBottom] = useState(false);
|
||||||
|
const headerBackgroundColor = useMemo(() => {
|
||||||
|
if (themeMode === 'dark') {
|
||||||
|
return group.color_code_dark || group.color_code || '#1e1e1e';
|
||||||
|
}
|
||||||
|
return group.color_code || '#f5f5f5';
|
||||||
|
}, [themeMode, group.color_code, group.color_code_dark]);
|
||||||
|
|
||||||
const headerBackgroundColor = useMemo(() => {
|
return (
|
||||||
if (themeMode === 'dark') {
|
<div className="enhanced-kanban-group">
|
||||||
return group.color_code_dark || group.color_code || '#1e1e1e';
|
<div
|
||||||
}
|
className="enhanced-kanban-group-header"
|
||||||
return group.color_code || '#f5f5f5';
|
style={{
|
||||||
}, [themeMode, group.color_code, group.color_code_dark]);
|
backgroundColor: headerBackgroundColor,
|
||||||
|
}}
|
||||||
|
draggable
|
||||||
|
onDragStart={e => onGroupDragStart(e, group.id)}
|
||||||
|
onDragOver={onGroupDragOver}
|
||||||
|
onDrop={e => onGroupDrop(e, group.id)}
|
||||||
|
>
|
||||||
|
<h3>{group.name}</h3>
|
||||||
|
<span className="task-count">{group.tasks.length}</span>
|
||||||
|
</div>
|
||||||
|
<div className="enhanced-kanban-group-tasks">
|
||||||
|
{/* If group is empty, render a drop zone */}
|
||||||
|
{group.tasks.length === 0 && (
|
||||||
|
<div
|
||||||
|
className="empty-drop-zone"
|
||||||
|
style={{
|
||||||
|
padding: 8,
|
||||||
|
height: 500,
|
||||||
|
background: themeWiseColor(
|
||||||
|
'linear-gradient( 180deg, #fafafa, rgba(245, 243, 243, 0))',
|
||||||
|
'linear-gradient( 180deg, #2a2b2d, rgba(42, 43, 45, 0))',
|
||||||
|
themeMode
|
||||||
|
),
|
||||||
|
borderRadius: 6,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
paddingTop: 8,
|
||||||
|
color: '#888',
|
||||||
|
fontStyle: 'italic',
|
||||||
|
}}
|
||||||
|
onDragOver={e => { e.preventDefault(); onTaskDragOver(e, group.id, 0); }}
|
||||||
|
onDrop={e => { e.preventDefault(); onTaskDrop(e, group.id, 0); }}
|
||||||
|
>
|
||||||
|
{/* Drop indicator at the end of the group */}
|
||||||
|
{hoveredGroupId === group.id && hoveredTaskIdx === group.tasks.length && (
|
||||||
|
<div className="drop-preview-indicator">
|
||||||
|
<div className="drop-line" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{(isOwnerOrAdmin || isProjectManager) && !showNewCardTop && !showNewCardBottom && (
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
style={{
|
||||||
|
height: '38px',
|
||||||
|
width: '100%',
|
||||||
|
borderRadius: 6,
|
||||||
|
boxShadow: 'none',
|
||||||
|
}}
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
setShowNewCardBottom(false);
|
||||||
|
setShowNewCardTop(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('addTask')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{showNewCardTop && <EnhancedKanbanCreateTaskCard sectionId={group.id} setShowNewCard={setShowNewCardTop} position='top' />}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
return (
|
{/* Drop indicator at the top of the group */}
|
||||||
<div className="enhanced-kanban-group">
|
{hoveredGroupId === group.id && hoveredTaskIdx === 0 && (
|
||||||
<div
|
<div className="drop-preview-indicator">
|
||||||
className="enhanced-kanban-group-header"
|
<div className="drop-line" />
|
||||||
style={{
|
</div>
|
||||||
backgroundColor: headerBackgroundColor,
|
)}
|
||||||
}}
|
|
||||||
draggable
|
|
||||||
onDragStart={e => onGroupDragStart(e, group.id)}
|
|
||||||
onDragOver={onGroupDragOver}
|
|
||||||
onDrop={e => onGroupDrop(e, group.id)}
|
|
||||||
>
|
|
||||||
<h3>{group.name}</h3>
|
|
||||||
<span className="task-count">{group.tasks.length}</span>
|
|
||||||
</div>
|
|
||||||
<div className="enhanced-kanban-group-tasks">
|
|
||||||
{/* Drop indicator at the top of the group */}
|
|
||||||
{hoveredGroupId === group.id && hoveredTaskIdx === 0 && (
|
|
||||||
<div className="drop-preview-indicator">
|
|
||||||
<div className="drop-line" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{group.tasks.map((task, idx) => (
|
{group.tasks.map((task, idx) => (
|
||||||
<React.Fragment key={task.id}>
|
<React.Fragment key={task.id}>
|
||||||
<TaskCard
|
<TaskCard
|
||||||
task={task}
|
task={task}
|
||||||
onTaskDragStart={onTaskDragStart}
|
onTaskDragStart={onTaskDragStart}
|
||||||
onTaskDragOver={onTaskDragOver}
|
onTaskDragOver={onTaskDragOver}
|
||||||
onTaskDrop={onTaskDrop}
|
onTaskDrop={onTaskDrop}
|
||||||
groupId={group.id}
|
groupId={group.id}
|
||||||
isDropIndicator={hoveredGroupId === group.id && hoveredTaskIdx === idx}
|
isDropIndicator={hoveredGroupId === group.id && hoveredTaskIdx === idx}
|
||||||
idx={idx}
|
idx={idx}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
|
{(isOwnerOrAdmin || isProjectManager) && !showNewCardTop && !showNewCardBottom && group.tasks.length > 0 && (
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
style={{
|
||||||
|
height: '40px',
|
||||||
|
width: '100%',
|
||||||
|
borderRadius: 6,
|
||||||
|
boxShadow: 'none',
|
||||||
|
marginTop: 8,
|
||||||
|
}}
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
setShowNewCardBottom(true);
|
||||||
|
setShowNewCardTop(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('addTask')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{showNewCardBottom && <EnhancedKanbanCreateTaskCard sectionId={group.id} setShowNewCard={setShowNewCardBottom} position='bottom' />}
|
||||||
|
|
||||||
{/* Drop indicator at the end of the group */}
|
{/* Drop indicator at the end of the group */}
|
||||||
{hoveredGroupId === group.id && hoveredTaskIdx === group.tasks.length && (
|
{hoveredGroupId === group.id && hoveredTaskIdx === group.tasks.length && (
|
||||||
<div className="drop-preview-indicator">
|
<div className="drop-preview-indicator">
|
||||||
<div className="drop-line" />
|
<div className="drop-line" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
KanbanGroup.displayName = 'KanbanGroup';
|
KanbanGroup.displayName = 'KanbanGroup';
|
||||||
|
|||||||
Reference in New Issue
Block a user