diff --git a/frontend/package.json b/frontend/package.json
index f057dce..44e4b66 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -42,6 +42,7 @@
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-bootstrap": "^2.9.1",
+ "react-datepicker": "^4.23.0",
"react-datetime-picker": "^5.5.3",
"react-dom": "^18.2.0",
"react-icons": "^4.11.0",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 22fdadd..2bb6ef0 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -101,6 +101,9 @@ dependencies:
react-bootstrap:
specifier: ^2.9.1
version: 2.9.1(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
+ react-datepicker:
+ specifier: ^4.23.0
+ version: 4.23.0(react-dom@18.2.0)(react@18.2.0)
react-datetime-picker:
specifier: ^5.5.3
version: 5.5.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
@@ -3473,6 +3476,22 @@ packages:
- '@types/react-dom'
dev: false
+ /react-datepicker@4.23.0(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-w+msqlOZ14v6H1UknTKtZw/dw9naFMgAOspf59eY130gWpvy5dvKj/bgsFICDdvxB7PtKWxDcbGlAqCloY1d2A==}
+ peerDependencies:
+ react: ^16.9.0 || ^17 || ^18
+ react-dom: ^16.9.0 || ^17 || ^18
+ dependencies:
+ '@popperjs/core': 2.11.8
+ classnames: 2.3.2
+ date-fns: 2.30.0
+ prop-types: 15.8.1
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ react-onclickoutside: 6.13.0(react-dom@18.2.0)(react@18.2.0)
+ react-popper: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0)(react@18.2.0)
+ dev: false
+
/react-datetime-picker@5.5.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-bWGEPwGrZjaXTB8P4pbTSDygctLaqTWp0nNibaz8po+l4eTh9gv3yiJ+n4NIcpIJDqZaQJO57Bnij2rAFVQyLw==}
peerDependencies:
@@ -3520,6 +3539,10 @@ packages:
scheduler: 0.23.0
dev: false
+ /react-fast-compare@3.2.2:
+ resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
+ dev: false
+
/react-fit@1.7.1(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-y/TYovCCBzfIwRJsbLj0rH4Es40wPQhU5GPPq9GlbdF09b0OdzTdMSkBza0QixSlgFzTm6dkM7oTFzaVvaBx+w==}
peerDependencies:
@@ -3565,6 +3588,30 @@ packages:
resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
dev: false
+ /react-onclickoutside@6.13.0(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==}
+ peerDependencies:
+ react: ^15.5.x || ^16.x || ^17.x || ^18.x
+ react-dom: ^15.5.x || ^16.x || ^17.x || ^18.x
+ dependencies:
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
+ /react-popper@2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==}
+ peerDependencies:
+ '@popperjs/core': ^2.0.0
+ react: ^16.8.0 || ^17 || ^18
+ react-dom: ^16.8.0 || ^17 || ^18
+ dependencies:
+ '@popperjs/core': 2.11.8
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ react-fast-compare: 3.2.2
+ warning: 4.0.3
+ dev: false
+
/react-redux@7.2.9(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==}
peerDependencies:
diff --git a/frontend/src/components/kanbanBoard/taskCard.jsx b/frontend/src/components/kanbanBoard/taskCard.jsx
index e631a26..4858d82 100644
--- a/frontend/src/components/kanbanBoard/taskCard.jsx
+++ b/frontend/src/components/kanbanBoard/taskCard.jsx
@@ -1,16 +1,14 @@
import { useState } from "react";
-import { useEffect } from "react";
-import { BsFillTrashFill } from "react-icons/bs";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { TaskDetailModal } from "./taskDetailModal";
+import { GoChecklist, GoArchive } from "react-icons/go";
export function TaskCard({ task, deleteTask, updateTask }) {
+ // State to track if the mouse is over the task card
const [mouseIsOver, setMouseIsOver] = useState(false);
- // console.log(task.challenge);
- // console.log(task.importance);
- // console.log(task.difficulty);
+ // DnD Kit hook for sortable items
const { setNodeRef, attributes, listeners, transform, transition, isDragging } = useSortable({
id: task.id,
data: {
@@ -18,68 +16,157 @@ export function TaskCard({ task, deleteTask, updateTask }) {
task,
},
});
+
+ // Style for the task card, adjusting for dragging animation
const style = {
transition,
transform: CSS.Transform.toString(transform),
};
+ // ---- DESC AND TAG ---- */
+ // Tags
+ const tags =
+ task.tags.length > 0 ? (
+
+ {task.tags.map((tag, index) => (
+
+ {tag.label}
+
+ ))}
+
+ ) : null;
- {
- /* If card is dragged */
- }
+ // difficulty?
+ const difficultyTag = task.difficulty ? (
+
+ difficulty
+
+ ) : null;
+
+ // Due Date
+ const dueDateTag =
+ task.end_event && new Date(task.end_event) > new Date()
+ ? (() => {
+ const daysUntilDue = Math.ceil((new Date(task.end_event) - new Date()) / (1000 * 60 * 60 * 24));
+
+ let colorClass =
+ daysUntilDue >= 365
+ ? "gray-200"
+ : daysUntilDue >= 30
+ ? "blue-200"
+ : daysUntilDue >= 7
+ ? "green-200"
+ : daysUntilDue > 0
+ ? "yellow-200"
+ : "red-200";
+
+ const formattedDueDate =
+ daysUntilDue >= 365
+ ? new Date(task.end_event).toLocaleDateString("en-US", {
+ day: "numeric",
+ month: "short",
+ year: "numeric",
+ })
+ : new Date(task.end_event).toLocaleDateString("en-US", { day: "numeric", month: "short" });
+
+ return (
+
+ Due: {formattedDueDate}
+
+ );
+ })()
+ : null;
+
+ // Subtask count
+ const subtaskCountTag = task.subtaskCount ? (
+
+ {task.subtaskCount}
+
+ ) : null;
+
+ // ---- DRAG STATE ---- */
+
+ // If the card is being dragged
if (isDragging) {
return (
);
}
+ // If the card is not being dragged
return (
+ {/* Task Detail Modal */}
+
+ {/* -------- Task Card -------- */}
{
setMouseIsOver(true);
}}
onMouseLeave={() => {
setMouseIsOver(false);
}}>
-
document.getElementById(`task_detail_modal_${task.id}`).showModal()}>
- {task.content}
-
-
- {mouseIsOver && (
-
- )}
+ {/* -------- Task Content -------- */}
+ {/* Tags */}
+ {tags}
+
+ {/* Title */}
+
document.getElementById(`task_detail_modal_${task.id}`).showModal()}>
+ {task.content}
+
+ {/* -------- Archive Task Button -------- */}
+ {mouseIsOver && (
+
+ )}
+
+ {/* Description */}
+
+ {difficultyTag}
+ {dueDateTag}
+ {subtaskCountTag}
+
);
-}
\ No newline at end of file
+}
diff --git a/frontend/src/components/kanbanBoard/taskDetailModal.jsx b/frontend/src/components/kanbanBoard/taskDetailModal.jsx
index d349c3d..e4317e5 100644
--- a/frontend/src/components/kanbanBoard/taskDetailModal.jsx
+++ b/frontend/src/components/kanbanBoard/taskDetailModal.jsx
@@ -2,14 +2,19 @@ import { useState } from "react";
import { FaTasks, FaRegListAlt } from "react-icons/fa";
import { FaPlus } from "react-icons/fa6";
import { TbChecklist } from "react-icons/tb";
+import DatePicker from "react-datepicker";
+import "react-datepicker/dist/react-datepicker.css";
-export function TaskDetailModal({ title, description, tags, difficulty, challenge, importance, taskId }) {
+export function TaskDetailModal({ title, description, tags, difficulty, challenge, importance, taskId, updateTask }) {
const [isChallengeChecked, setChallengeChecked] = useState(challenge);
const [isImportantChecked, setImportantChecked] = useState(importance);
const [currentDifficulty, setCurrentDifficulty] = useState(difficulty);
- // console.log(currentDifficulty);
- // console.log(isChallengeChecked);
- // console.log(isImportantChecked);
+ const [selectedTags, setSelectedTags] = useState([]);
+ const [dateStart, setDateStart] = useState(new Date());
+ const [dateEnd, setDateEnd] = useState(new Date());
+ const [startDateEnabled, setStartDateEnabled] = useState(false);
+ const [endDateEnabled, setEndDateEnabled] = useState(false);
+ const [isTaskComplete, setTaskComplete] = useState(false);
const handleChallengeChange = () => {
setChallengeChecked(!isChallengeChecked);
@@ -23,6 +28,51 @@ export function TaskDetailModal({ title, description, tags, difficulty, challeng
setCurrentDifficulty(parseInt(event.target.value, 10));
};
+ const handleTagChange = (tag) => {
+ const isSelected = selectedTags.includes(tag);
+ setSelectedTags(isSelected ? selectedTags.filter((selectedTag) => selectedTag !== tag) : [...selectedTags, tag]);
+ };
+
+ const handleStartDateChange = () => {
+ if (!isTaskComplete) {
+ setStartDateEnabled(!startDateEnabled);
+ }
+ };
+
+ const handleEndDateChange = () => {
+ if (!isTaskComplete) {
+ setEndDateEnabled(!endDateEnabled);
+ }
+ };
+
+ const handleTaskCompleteChange = () => {
+ if (isTaskComplete) {
+ setTaskComplete(false);
+ } else {
+ setTaskComplete(true);
+ setStartDateEnabled(false);
+ setEndDateEnabled(false);
+ }
+ };
+
+ // Existing tags
+ const existingTags = tags.map((tag, index) => (
+
+ {tag.label}
+
+ ));
+
+ // Selected tags
+ const selectedTagElements = selectedTags.map((tag, index) => (
+
+ {tag.label}
+
+ ));
+
return (