Skip to content

Commit 04b2df5

Browse files
author
raineer
committed
deadline taskaddmodal
1 parent a015d06 commit 04b2df5

9 files changed

Lines changed: 364 additions & 457 deletions

File tree

‎apps/codebility/app/home/kanban/[projectId]/[id]/_components/KanbanTask.tsx‎

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import React, { useEffect, useState, useMemo, memo } from "react";
2-
import Image from "next/image";
32
import DefaultAvatar from "@/components/DefaultAvatar";
43
import {
54
IconPriority1,
@@ -15,24 +14,16 @@ import { CSS } from "@dnd-kit/utilities";
1514

1615
import KanbanTaskViewEditModal from "./kanban_modals/KanbanTaskViewEditModal";
1716

18-
interface CodevMember {
17+
// Local interface for fetched sidekick data
18+
interface SidekickMember {
1919
id: string;
2020
first_name: string;
2121
last_name: string;
2222
image_url?: string | null;
2323
}
2424

25-
interface SkillCategory {
26-
name: string;
27-
}
28-
29-
interface ExtendedTask extends Task {
30-
codev?: CodevMember;
31-
skill_category?: SkillCategory;
32-
}
33-
3425
interface Props {
35-
task: ExtendedTask;
26+
task: Task; // Use the global Task type, not ExtendedTask
3627
columnId?: string;
3728
onComplete?: (taskId: string) => void;
3829
}
@@ -77,9 +68,36 @@ function KanbanTask({ task, columnId, onComplete }: Props) {
7768
: IconPriority5
7869
, [task.priority, PriorityIconMap]);
7970

80-
const [sidekickDetails, setSidekickDetails] = useState<CodevMember[]>([]);
71+
const [sidekickDetails, setSidekickDetails] = useState<SidekickMember[]>([]);
8172
const [supabase, setSupabase] = useState<any>(null);
8273

74+
// Calculate deadline urgency
75+
const getDeadlineStatus = useMemo(() => {
76+
if (!task.deadline) return null;
77+
78+
const today = new Date();
79+
today.setHours(0, 0, 0, 0);
80+
81+
const deadlineDate = new Date(task.deadline);
82+
deadlineDate.setHours(0, 0, 0, 0);
83+
84+
const diffTime = deadlineDate.getTime() - today.getTime();
85+
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
86+
87+
if (diffDays < 0) {
88+
return { status: 'overdue', text: 'Overdue', color: 'bg-red-600 text-white border-red-700' };
89+
} else if (diffDays === 0) {
90+
return { status: 'today', text: 'Due Today', color: 'bg-orange-600 text-white border-orange-700' };
91+
} else if (diffDays <= 3) {
92+
return { status: 'urgent', text: `${diffDays}d left`, color: 'bg-yellow-500 text-gray-900 border-yellow-600' };
93+
} else if (diffDays <= 7) {
94+
return { status: 'soon', text: `${diffDays}d left`, color: 'bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-900/40 dark:text-blue-300' };
95+
} else {
96+
const dateStr = deadlineDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
97+
return { status: 'normal', text: dateStr, color: 'bg-gray-100 text-gray-700 border-gray-200 dark:bg-gray-800 dark:text-gray-300' };
98+
}
99+
}, [task.deadline]);
100+
83101
// Get priority-based styles
84102
const priorityStyles = useMemo(() => {
85103
const baseClasses = "absolute top-0 left-0 w-1 h-full rounded-l-lg";
@@ -151,7 +169,7 @@ function KanbanTask({ task, columnId, onComplete }: Props) {
151169
if (error) {
152170
console.error("Error fetching sidekick details:", error.message);
153171
} else if (data) {
154-
setSidekickDetails(data as CodevMember[]);
172+
setSidekickDetails(data as SidekickMember[]);
155173
}
156174
}
157175
fetchSidekickDetails();
@@ -205,7 +223,7 @@ function KanbanTask({ task, columnId, onComplete }: Props) {
205223
<span className="capitalize">{task.priority ?? "Low"}</span>
206224
<PriorityIcon className="h-3 w-3 md:h-4 md:w-4" />
207225
</div>
208-
{/* PRIMARY AVATAR - Fixed alignment */}
226+
{/* PRIMARY AVATAR */}
209227
<div
210228
className="flex h-7 w-7 items-center justify-center overflow-hidden rounded-full border-2 border-white shadow-sm transition-all duration-200
211229
group-hover:border-customBlue-300 group-hover:scale-110 dark:border-gray-700 md:h-8 md:w-8"
@@ -265,9 +283,18 @@ function KanbanTask({ task, columnId, onComplete }: Props) {
265283
{task.type}
266284
</span>
267285
)}
286+
{/* Deadline Badge */}
287+
{getDeadlineStatus && (
288+
<span
289+
className={`inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium shadow-sm border md:px-2.5 md:py-1 ${getDeadlineStatus.color} transition-all duration-200`}
290+
title={`Deadline: ${new Date(task.deadline!).toLocaleDateString()}`}
291+
>
292+
📅 {getDeadlineStatus.text}
293+
</span>
294+
)}
268295
</div>
269296

270-
{/* Sidekicks - Fixed alignment */}
297+
{/* Sidekicks */}
271298
{sidekicks.length > 0 && (
272299
<div className="flex -space-x-1.5 pt-1 md:-space-x-2 md:pt-2">
273300
{sidekicks.slice(0, 3).map((sidekickId) => {

‎apps/codebility/app/home/kanban/[projectId]/[id]/_components/tasks/TaskAddModal.tsx‎

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ interface CodevMember {
5555
image_url?: string | null;
5656
}
5757

58-
// Fetch available members function - from TaskViewModal
58+
// Fetch available members
5959
const fetchAvailableMembers = async (
6060
projectId: string,
6161
): Promise<CodevMember[]> => {
@@ -314,6 +314,7 @@ const TaskAddModal = () => {
314314
const [isPending, startTransition] = useTransition();
315315
const [description, setDescription] = useState("");
316316
const [selectedDifficulty, setSelectedDifficulty] = useState<string>("");
317+
const [deadline, setDeadline] = useState<string>(""); // NEW: deadline state
317318
const user = useUserStore((state) => state.user);
318319
const [supabase, setSupabase] = useState<any>(null);
319320

@@ -338,6 +339,7 @@ const TaskAddModal = () => {
338339
setDescription("");
339340
setMainAssignee("");
340341
setSidekicks([]);
342+
setDeadline(""); // NEW: reset deadline
341343
onClose();
342344
};
343345

@@ -382,6 +384,11 @@ const TaskAddModal = () => {
382384
const calculatedPoints = getPointsFromDifficulty(selectedDifficulty);
383385
formData.set("points", calculatedPoints.toString());
384386

387+
// NEW: Add deadline to formData
388+
if (deadline) {
389+
formData.append("deadline", deadline);
390+
}
391+
385392
const image = formData.get("image");
386393
if (image && image instanceof File) {
387394
formData.append("image", image);
@@ -400,8 +407,6 @@ const TaskAddModal = () => {
400407
if (response.success) {
401408
toast.success("Task created successfully");
402409
handleClose();
403-
404-
// Refetch the board data
405410
await fetchBoardData();
406411
} else {
407412
toast.error(response.error || "Failed to create task");
@@ -458,6 +463,21 @@ const TaskAddModal = () => {
458463
/>
459464
</div>
460465

466+
{/* NEW: Deadline Input */}
467+
<div className="space-y-2">
468+
<Label htmlFor="deadline" className="text-sm font-medium">
469+
Deadline <span className="text-gray-500 text-xs">(Optional)</span>
470+
</Label>
471+
<Input
472+
id="deadline"
473+
name="deadline"
474+
type="date"
475+
value={deadline}
476+
onChange={(e) => setDeadline(e.target.value)}
477+
className="bg-light-900 dark:bg-dark-200 dark:text-light-900 focus:border-customBlue-500 border border-gray-300"
478+
/>
479+
</div>
480+
461481
<div className="space-y-2">
462482
<div className="flex items-center gap-2">
463483
<Label htmlFor="points" className="text-sm font-medium">
@@ -476,6 +496,7 @@ const TaskAddModal = () => {
476496
readOnly
477497
/>
478498
</div>
499+
479500
<div className="space-y-2">
480501
<Label className="text-sm font-medium">Priority</Label>
481502
<Select name="priority">

‎apps/codebility/app/home/kanban/[projectId]/[id]/_components/tasks/TaskEditModal.tsx‎

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -299,9 +299,9 @@ const TaskEditModal = () => {
299299
skill_category_id: "",
300300
codev_id: "",
301301
projectId: "",
302+
deadline: "",
302303
});
303304

304-
// Calculate points based on difficulty
305305
const getPointsFromDifficulty = (difficulty: string): number => {
306306
return DIFFICULTY_POINTS[difficulty as keyof typeof DIFFICULTY_POINTS] || 0;
307307
};
@@ -311,12 +311,12 @@ const TaskEditModal = () => {
311311
content: taskData.description || "",
312312
immediatelyRender: false,
313313
});
314+
314315
useEffect(() => {
315316
const supabaseClient = createClientClientComponent();
316317
setSupabase(supabaseClient);
317318
}, []);
318319

319-
// Fetch skill categories
320320
useEffect(() => {
321321
if (!supabase) return;
322322

@@ -341,17 +341,15 @@ const TaskEditModal = () => {
341341
if (isModalOpen && data) {
342342
const fetchProjectData = async () => {
343343
try {
344-
// Get board_id from kanban_column
345344
const { data: column } = await supabase
346345
.from("kanban_columns")
347346
.select("board_id")
348347
.eq("id", data.kanban_column_id)
349348
.single();
350349

351350
if (column?.board_id) {
352-
setBoardId(column.board_id); // Store the board ID
351+
setBoardId(column.board_id);
353352

354-
// Get project_id from kanban_board
355353
const { data: board } = await supabase
356354
.from("kanban_boards")
357355
.select("project_id")
@@ -376,6 +374,7 @@ const TaskEditModal = () => {
376374
skill_category_id: data.skill_category?.id || "",
377375
codev_id: data.codev?.id || "",
378376
projectId: board.project_id,
377+
deadline: data.deadline || "",
379378
});
380379
}
381380
}
@@ -395,7 +394,6 @@ const TaskEditModal = () => {
395394
) => {
396395
setTaskData((prev) => {
397396
const updated = { ...prev, [key]: value };
398-
// Auto-calculate points when difficulty changes
399397
if (key === "difficulty" && typeof value === "string") {
400398
updated.points = getPointsFromDifficulty(value);
401399
}
@@ -430,6 +428,7 @@ const TaskEditModal = () => {
430428
formData.append("points", String(taskData.points || 0));
431429
formData.append("skill_category_id", taskData.skill_category_id);
432430
formData.append("codev_id", taskData.codev_id ?? "null");
431+
formData.append("deadline", taskData.deadline || "null");
433432

434433
if (taskData.sidekick_ids && taskData.sidekick_ids.length > 0) {
435434
formData.append("sidekick_ids", taskData.sidekick_ids.join(","));
@@ -483,6 +482,20 @@ const TaskEditModal = () => {
483482
/>
484483
</div>
485484

485+
<div className="space-y-2">
486+
<Label htmlFor="deadline" className="text-sm font-medium">
487+
Deadline <span className="text-gray-500 text-xs">(Optional)</span>
488+
</Label>
489+
<Input
490+
id="deadline"
491+
name="deadline"
492+
type="date"
493+
value={taskData.deadline || ""}
494+
onChange={(e) => handleInputChange("deadline", e.target.value)}
495+
className="focus:border-customBlue-500 border-gray-300 dark:border-gray-700"
496+
/>
497+
</div>
498+
486499
<div className="space-y-2">
487500
<div className="flex items-center gap-2">
488501
<Label htmlFor="points" className="text-sm font-medium">
@@ -664,34 +677,32 @@ const TaskEditModal = () => {
664677
type="button"
665678
variant="outline"
666679
onClick={onClose}
667-
// className="text-md bg-customBlue-100 hover:bg-customBlue-200 focus-visible:ring-customBlue-100 flex h-10 w-full items-center justify-center gap-2 whitespace-nowrap rounded-md px-6 py-1 text-white ring-offset-background transition-colors duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 sm:w-auto lg:text-lg"
668680
style={{
669-
backgroundColor: "#2563EB",
670-
color: "white",
671-
padding: "6px 16px",
672-
fontSize: "14px",
673-
borderRadius: "4px",
674-
border: "none",
675-
minWidth: "auto",
676-
width: "auto",
677-
}}
681+
backgroundColor: "#2563EB",
682+
color: "white",
683+
padding: "6px 16px",
684+
fontSize: "14px",
685+
borderRadius: "4px",
686+
border: "none",
687+
minWidth: "auto",
688+
width: "auto",
689+
}}
678690
disabled={loading}
679691
>
680692
Cancel
681693
</Button>
682694
<Button
683695
type="submit"
684-
// className="text-md bg-customBlue-100 hover:bg-customBlue-200 focus-visible:ring-customBlue-100 flex h-10 w-full items-center justify-center gap-2 whitespace-nowrap rounded-md px-6 py-1 text-white ring-offset-background transition-colors duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 sm:w-auto lg:text-lg"
685696
style={{
686-
backgroundColor: "#2563EB",
687-
color: "white",
688-
padding: "6px 16px",
689-
fontSize: "14px",
690-
borderRadius: "4px",
691-
border: "none",
692-
minWidth: "auto",
693-
width: "auto",
694-
}}
697+
backgroundColor: "#2563EB",
698+
color: "white",
699+
padding: "6px 16px",
700+
fontSize: "14px",
701+
borderRadius: "4px",
702+
border: "none",
703+
minWidth: "auto",
704+
width: "auto",
705+
}}
695706
disabled={loading}
696707
>
697708
{loading ? "Saving..." : "Save Changes"}

0 commit comments

Comments
 (0)