Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import BoardClosedBanner from '@/components/board/BoardClosedBanner';
import BoardNavbar from '@/components/board/BoardNavbar';
import DroppableColumn from '@/components/board/DroppableColumn';
import TaskDetailModal from '@/components/board/TaskDetailModal';
import LoadingContent from '@/components/ui/LoadingContent';
import { useBoardData } from '@/hooks/useBoardData';
import { useBoardOperations } from '@/hooks/useBoardOperations';
Expand All @@ -11,7 +10,7 @@
import { selectActiveColumns } from '@/store/selectors/columnsSelector';
import { selectTaskById, selectTasksByColumns } from '@/store/selectors/tasksSelectors';
import { columnsReordered, setColumns } from '@/store/slices/columnsSlice';
import type { Column, Task } from '@/types';
import type { Column } from '@/types';
import {
DndContext,
DragOverlay,
Expand All @@ -33,16 +32,14 @@
sortableKeyboardCoordinates,
} from '@dnd-kit/sortable';
import { GripVertical, Plus } from 'lucide-react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import { useParams } from 'react-router-dom';

const WorkspaceBoard = () => {

const { boardId } = useParams();
const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null);
const [showDetailModal, setShowDetailModal] = useState(false);
const [activeItem, setActiveItem] = useState<Task | null>(null);
const containerRef = useRef<HTMLDivElement>(null);
const dispatch = useDispatch();
const columns = useAppSelector(selectActiveColumns);
Expand Down Expand Up @@ -158,7 +155,7 @@
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [activeInputColumnId, isBoardClosed]);

Check warning on line 158 in frontend/src/components/board/BoardContent.tsx

View workflow job for this annotation

GitHub Actions / build-and-deploy

React Hook useEffect has missing dependencies: 'setActiveInputColumnId' and 'setCardTitle'. Either include them or remove the dependency array

const handleDragStart = useCallback(
(event: DragStartEvent) => {
Expand Down Expand Up @@ -186,7 +183,7 @@
setActiveInputColumnId(null);
setCardTitle('');
},
[columns, isBoardClosed]

Check warning on line 186 in frontend/src/components/board/BoardContent.tsx

View workflow job for this annotation

GitHub Actions / build-and-deploy

React Hook useCallback has missing dependencies: 'setActiveInputColumnId' and 'setCardTitle'. Either include them or remove the dependency array
);

// OPTIMIZATION: Heavily optimized drag over with batching and debouncing
Expand Down Expand Up @@ -307,7 +304,7 @@
}
}, 16); // OPTIMIZATION: 16ms delay = ~60fps batching
},
[isDragging, isBoardClosed]

Check warning on line 307 in frontend/src/components/board/BoardContent.tsx

View workflow job for this annotation

GitHub Actions / build-and-deploy

React Hook useCallback has a missing dependency: 'dispatch'. Either include it or remove the dependency array
);

const handleDragEnd = useCallback((event: DragEndEvent) => {
Expand Down Expand Up @@ -399,24 +396,16 @@
}
}
}
}, [isBoardClosed]);

Check warning on line 399 in frontend/src/components/board/BoardContent.tsx

View workflow job for this annotation

GitHub Actions / build-and-deploy

React Hook useCallback has missing dependencies: 'boardId', 'columns', and 'dispatch'. Either include them or remove the dependency array

const handleStartAddingCard = useCallback((columnId: number) => {
if (isBoardClosed) return;

setActiveInputColumnId(columnId);
setCardTitle('');
}, [isBoardClosed]);

Check warning on line 406 in frontend/src/components/board/BoardContent.tsx

View workflow job for this annotation

GitHub Actions / build-and-deploy

React Hook useCallback has missing dependencies: 'setActiveInputColumnId' and 'setCardTitle'. Either include them or remove the dependency array

const handleHideDetailModal = useCallback(() => {
setShowDetailModal(false);
}, []);

const handleShowDetailModal = useCallback(() => {
setShowDetailModal(true);
}, []);

console.log("worspace board")
console.log("worspace board: " + boardId);
return (
<div className='bg-[#283449] w-full h-full flex flex-col'>
{
Expand Down Expand Up @@ -457,8 +446,6 @@
onUpdateColumnTitle={updateColumnTitle}
onArchiveColumn={archiveColumn}
onArchiveAllItems={archiveAllTasksInColumn}
handleShowDetailTask={handleShowDetailModal}
setActiveItem={setActiveItem}
/>
))}
</SortableContext>
Expand Down Expand Up @@ -501,20 +488,10 @@
</DragOverlay>
</DndContext>
</div>
{
showDetailModal &&
<TaskDetailModal
onClose={handleHideDetailModal}
itemId={activeItem?.id || 0}
// item={activeItem}
onArchive={archiveTask}
onUpdate={updateTask}
/>
}
</>
}
</div>
);
};

export default WorkspaceBoard;
export default memo(WorkspaceBoard);
12 changes: 4 additions & 8 deletions frontend/src/components/board/DraggableItem.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
import type { Item, Task } from '@/types';
import { detectUrl } from '@/utils/UrlPreviewUtils';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { X } from 'lucide-react';
import UrlPreview from './UrlPreview';
import { useAppSelector, type RootState } from '@/store';
import { useAppDispatch, useAppSelector, type RootState } from '@/store';
import { useSelector } from 'react-redux';
import { selectTaskById } from '@/store/selectors/tasksSelectors';
import { showTaskModal } from '@/store/slices/taskModalSlice';

interface DraggableItemProps {
onDelete: (itemId: number) => void;
handleShowDetailTask: () => void;
label?: string; // e.g., "FE", "BE"
setActiveItem: (item: Task) => void;
itemId: number;
}

const DraggableItem: React.FC<DraggableItemProps> = ({
onDelete,
handleShowDetailTask,
label,
setActiveItem,
itemId
}) => {
const item = useAppSelector((state) => selectTaskById(itemId)(state));
Expand All @@ -45,6 +41,7 @@ const DraggableItem: React.FC<DraggableItemProps> = ({
};

const members = useSelector((state: RootState) => state.members);
const dispatch = useAppDispatch();

// console.log("Rendering DraggableItem:", item.id, item.name);
return (
Expand All @@ -54,8 +51,7 @@ const DraggableItem: React.FC<DraggableItemProps> = ({
{...attributes}
{...listeners}
onClick={() => {
detectUrl(item.name) ? undefined : handleShowDetailTask();
setActiveItem(item);
detectUrl(item.name) ? undefined : dispatch(showTaskModal(item.id));
}}
className={`
select-none bg-[#222f44] p-2 rounded-lg
Expand Down
6 changes: 0 additions & 6 deletions frontend/src/components/board/DroppableColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@
onUpdateColumnTitle: (columnId: number, newTitle: string) => void;
onArchiveColumn: (column: Column) => void;
onArchiveAllItems: (columnId: number) => void;
handleShowDetailTask: () => void;
setActiveItem: (item: Task) => void;
}

const DroppableColumnComponent: React.FC<DroppableColumnProps> = ({
Expand All @@ -49,8 +47,6 @@
onUpdateColumnTitle,
onArchiveColumn,
onArchiveAllItems,
handleShowDetailTask,
setActiveItem,
}) => {
const [isEditingTitle, setIsEditingTitle] = useState(false);
const [showOptionsMenu, setShowOptionsMenu] = useState(false);
Expand Down Expand Up @@ -123,7 +119,7 @@
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isEditingTitle]);

Check warning on line 122 in frontend/src/components/board/DroppableColumn.tsx

View workflow job for this annotation

GitHub Actions / build-and-deploy

React Hook useEffect has a missing dependency: 'handleTitleSubmit'. Either include it or remove the dependency array

const handleAddCard = useCallback(() => {
// Close title editing and options menu when starting to add card
Expand All @@ -134,7 +130,7 @@
setShowOptionsMenu(false);
}
onStartAddingCard(column.id);
}, [isEditingTitle, showOptionsMenu, column.id, onStartAddingCard]);

Check warning on line 133 in frontend/src/components/board/DroppableColumn.tsx

View workflow job for this annotation

GitHub Actions / build-and-deploy

React Hook useCallback has a missing dependency: 'handleTitleSubmit'. Either include it or remove the dependency array

const handleSubmit = useCallback(() => {
onSubmitCard(column.id);
Expand Down Expand Up @@ -296,8 +292,6 @@
key={item.id}
itemId={item.id}
onDelete={onDeleteItem}
handleShowDetailTask={handleShowDetailTask}
setActiveItem={setActiveItem}
/>
))}
</div>
Expand Down
77 changes: 54 additions & 23 deletions frontend/src/components/board/TaskDetailModal.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,66 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import {
X, Calendar, Users,
List, Edit3, Archive,
// Eye, Paperclip, Copy, Trash2, MessageSquareText
} from 'lucide-react';
import LoadingContent from '../ui/LoadingContent';
import type { ItemDetail } from '@/types';
import type { ItemDetail, UpdateItemRequest } from '@/types';
import taskService from '@/services/taskService';
import AssignMembers from './AssignMembers';
import { hideTaskModal } from '@/store/slices/taskModalSlice';
import { useAppDispatch } from '@/store';
import { useSelector } from 'react-redux';
import { selectTaskModalState } from '@/store/selectors/modalSelector';
import { notify } from '@/services/toastService';

interface TaskModalProps {
onClose: () => void;
itemId: number;
onUpdate: (itemId: number, updates: any) => void;
onArchive: (itemId: number) => void;
}

const TaskDetailModal: React.FC<TaskModalProps> = ({
onClose,
itemId,
onUpdate,
onArchive,
}) => {
const TaskDetailModal: React.FC = () => {
const [isLoading, setIsLoading] = useState(false);
const [item, setItem] = useState<ItemDetail>(null as any);
const [isEditingTitle, setIsEditingTitle] = useState(false);
const [isEditingDescription, setIsEditingDescription] = useState(false);
// const [comment, setComment] = useState('');

const dispatch = useAppDispatch();
const { taskId } = useSelector(selectTaskModalState);

const onModalClose = useCallback(() => {
dispatch(hideTaskModal());
}, [dispatch]);

const archiveTask = useCallback(
async (taskId: number) => {
try {
const result = await taskService.archiveTask(taskId);
notify.success(result.message);
} catch (error: any) {
notify.error(error.response?.data?.message || 'Failed to archive task');
}
},
[]
);

const updateTask = useCallback(
async (taskId: number, data: UpdateItemRequest) => {
try {
const result = await taskService.updateTask(taskId, data);
notify.success(result.message);
} catch (error: any) {
notify.error(error.response?.data?.message || 'Failed to update task');
}
},
[]
);

const handleUpdate = () => {
setIsEditingTitle(false);
onUpdate(item.id, item);
updateTask(item.id, { name: item.name, description: item.description as string });
};

const fetchTaskDetail = async () => {
setIsLoading(true);
try {
const response = await taskService.getTaskDetail(itemId);
const response = await taskService.getTaskDetail(Number(taskId));
setItem(response.data);
}
catch (error) {
Expand All @@ -48,12 +71,20 @@
}
};

console.log("Rendering TaskDetailModal for itemId:", itemId, item);
// Close modal on Escape key press
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") onModalClose();
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [onModalClose]);

console.log("Rendering TaskDetailModal for taskId:", taskId, item);

useEffect(() => {
fetchTaskDetail();
}, [itemId]);
}, [taskId]);

Check warning on line 87 in frontend/src/components/board/TaskDetailModal.tsx

View workflow job for this annotation

GitHub Actions / build-and-deploy

React Hook useEffect has a missing dependency: 'fetchTaskDetail'. Either include it or remove the dependency array

return (
<div className="fixed inset-0 bg-black/50 flex items-start justify-center z-40 pt-8 px-4">
Expand Down Expand Up @@ -88,7 +119,7 @@
</h1>
)}
<button
onClick={onClose}
onClick={onModalClose}
className="text-gray-400 hover:text-white p-1 rounded hover:bg-gray-600 hover:bg-opacity-50"
>
<X size={18} />
Expand Down Expand Up @@ -117,7 +148,7 @@
<div className="pl-6 flex flex-wrap gap-2">

{/* Member section */}
<AssignMembers assignedMembers={item?.assignedMembers} taskId={itemId}/>
<AssignMembers assignedMembers={item?.assignedMembers} taskId={Number(taskId)} />

{/* Labels Section */}
<div className="pb-4">
Expand Down Expand Up @@ -272,8 +303,8 @@
<button
className="w-full text-left px-2 py-2 text-sm text-gray-300 hover:bg-gray-600 hover:bg-opacity-50 rounded flex items-center gap-2"
onClick={() => {
onArchive(item.id);
onClose();
archiveTask(item.id);
onModalClose();
}}
>
<Archive size={14} />
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/pages/Board.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useSelector } from "react-redux";
import WorkspaceBoard from "../components/board/BoardContent";
import { selectTaskModalState } from "@/store/selectors/modalSelector";
import TaskDetailModal from "@/components/board/TaskDetailModal";

const Board = () => {
const { isTaskModalShow } = useSelector(selectTaskModalState);
return (
<>
<WorkspaceBoard />
{isTaskModalShow && <TaskDetailModal />}
</>
);
}

export default Board;
5 changes: 3 additions & 2 deletions frontend/src/router/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ import Template from '@/pages/Template';
import Home from '@/pages/Home';
import Member from '@/pages/workspace/Member';
import Setting from '@/pages/workspace/Setting';
import WorkspaceBoard from '@/pages/WorkspaceBoard';
import WorkspaceBoard from '@/components/board/BoardContent';
import HomeBoards from '@/pages/HomeBoards';
import Boards from '@/pages/workspace/Board';
import WorkspaceDetail from '@/pages/workspace/WorkspaceDetail';
import SearchPage from '@/pages/SearchPage';
import Board from '@/pages/Board';

// Layout wrappers for different roles
const AdminLayout = () => (
Expand Down Expand Up @@ -105,7 +106,7 @@ const router = createBrowserRouter([
},
{
path: 'board/:boardId',
element: <WorkspaceBoard />,
element: <Board />,
},
{
path: 'workspace',
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import tasksReducer from "./slices/tasksSlice";
import membersReducer from "./slices/membersSlice";
import archivedColumnsReducer from "./slices/archiveColumnsSlice";
import archivedTasksReducer from "./slices/archiveTasksSlice";
import taskModalReducer from "./slices/taskModalSlice";

import { useDispatch, useSelector, type TypedUseSelectorHook } from 'react-redux';

// Persist configuration
Expand All @@ -24,6 +26,7 @@ const rootReducer = combineReducers({
archivedColumns: archivedColumnsReducer,
archivedTasks: archivedTasksReducer,
members: membersReducer,
taskModal: taskModalReducer,
});

const persistedReducer = persistReducer(persistConfig, rootReducer);
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/store/selectors/modalSelector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type { RootState } from "..";

export const selectTaskModalState = (state: RootState) => state.taskModal;
17 changes: 17 additions & 0 deletions frontend/src/store/slices/taskModalSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createSlice } from '@reduxjs/toolkit';
const taskModalSlice = createSlice({
name: 'taskModal',
initialState: { isTaskModalShow: false, taskId: null },
reducers: {
showTaskModal: (state, action) => {
state.isTaskModalShow = true;
state.taskId = action.payload;
},
hideTaskModal: (state) => {
state.isTaskModalShow = false;
state.taskId = null;
}
}
});
export const { showTaskModal, hideTaskModal } = taskModalSlice.actions;
export default taskModalSlice.reducer;
Loading