Skip to content

Commit 3fd92d9

Browse files
Fixed task completion approval message bugs/issues to avoid getting wrong message display.
1 parent e1018ca commit 3fd92d9

2 files changed

Lines changed: 202 additions & 182 deletions

File tree

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

Lines changed: 73 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,9 @@ function AssigneeSelector({
158158
.includes(searchQuery.toLowerCase()),
159159
);
160160

161-
// Restore proper removal functionality for AssigneeSelector
162161
const handleRemove = () => {
163162
setLocalAssignee(null);
164-
onAssigneeChange([]); // Pass empty array to indicate no assignee
165-
setTimeout(() => setLocalAssignee(null), 0); // Ensure UI updates
163+
onAssigneeChange([]);
166164
};
167165

168166
const handleSelect = (memberId: string) => {
@@ -304,7 +302,6 @@ const TaskViewModal = ({
304302
}) => {
305303
const { isOpen, onOpen, onClose, type, data } = useModal();
306304
const user = useUserStore((state) => state.user);
307-
// Include removeTaskOptimistic in the destructured store methods
308305
const { fetchBoardData, removeTaskOptimistic } = useKanbanStore();
309306

310307
const [isLoading, setIsLoading] = useState(false);
@@ -325,7 +322,6 @@ const TaskViewModal = ({
325322
const [createdBy, setCreatedBy] = useState<CodevMember | null>(null);
326323
const [forceRefreshKey, setForceRefreshKey] = useState<string>("");
327324

328-
// Enhanced state management for assignee changes
329325
const [manualSaveChanges, setManualSaveChanges] = useState(false);
330326
const [isSavingChanges, setIsSavingChanges] = useState(false);
331327
const [pendingAssigneeId, setPendingAssigneeId] = useState<
@@ -378,35 +374,30 @@ const TaskViewModal = ({
378374
fetchBoardId();
379375
}, [task?.kanban_column_id, supabase]);
380376

377+
// FIXED: Consolidated state reset logic with single execution flow
381378
useEffect(() => {
382-
if (task?.id) {
383-
const taskPrLink = task?.pr_link || "";
384-
setPrLink(taskPrLink);
385-
setOriginalPrLink(taskPrLink);
386-
setPrimaryAssignee(null); // Reset to null first
387-
setManualSaveChanges(false);
388-
setPendingAssigneeId(undefined);
389-
setForceRefreshKey(Date.now().toString());
390-
391-
// Force reset the AssigneeSelector local state by resetting primaryAssignee
392-
setTimeout(() => {
393-
// Fetch actual assignee from task after reset
394-
if (task.codev_id || task?.codev?.id) {
395-
const assigneeId = task.codev_id || task.codev?.id || "";
396-
if (supabase && assigneeId) {
397-
supabase
398-
.from("codev")
399-
.select("id, first_name, last_name, image_url")
400-
.eq("id", assigneeId)
401-
.single()
402-
.then(({ data, error }) => {
403-
if (!error && data) {
404-
setPrimaryAssignee(data as CodevMember);
405-
}
406-
});
407-
}
408-
}
409-
}, 0);
379+
if (!task?.id || !supabase) return;
380+
381+
const taskPrLink = task?.pr_link || "";
382+
setPrLink(taskPrLink);
383+
setOriginalPrLink(taskPrLink);
384+
setManualSaveChanges(false);
385+
setPendingAssigneeId(undefined);
386+
setForceRefreshKey(Date.now().toString());
387+
388+
// Fetch and set assignee in one go
389+
const assigneeId = task.codev_id || task?.codev?.id;
390+
if (assigneeId) {
391+
supabase
392+
.from("codev")
393+
.select("id, first_name, last_name, image_url")
394+
.eq("id", assigneeId)
395+
.single()
396+
.then(({ data, error }: { data: any; error: any }) => {
397+
setPrimaryAssignee(!error && data ? (data as CodevMember) : null);
398+
});
399+
} else {
400+
setPrimaryAssignee(null);
410401
}
411402
}, [task?.id, supabase]);
412403

@@ -441,37 +432,6 @@ const TaskViewModal = ({
441432
fetchSidekickDetails();
442433
}, [task?.sidekick_ids, supabase]);
443434

444-
useEffect(() => {
445-
if (!supabase || !task) return;
446-
447-
const fetchPrimaryAssignee = async () => {
448-
const assigneeId = task?.codev_id || task?.codev?.id;
449-
450-
setPrimaryAssignee(null);
451-
452-
if (assigneeId) {
453-
const { data, error } = await supabase
454-
.from("codev")
455-
.select("id, first_name, last_name, image_url")
456-
.eq("id", assigneeId)
457-
.single();
458-
459-
if (!error && data) {
460-
setPrimaryAssignee(data as CodevMember);
461-
}
462-
} else if (task?.codev) {
463-
setPrimaryAssignee({
464-
id: task.codev.id,
465-
first_name: task.codev.first_name,
466-
last_name: task.codev.last_name,
467-
image_url: task.codev.image_url,
468-
});
469-
}
470-
};
471-
472-
fetchPrimaryAssignee();
473-
}, [task, supabase]);
474-
475435
useEffect(() => {
476436
if (!supabase || !task?.created_by) return;
477437

@@ -512,47 +472,62 @@ const TaskViewModal = ({
512472
setUpdateLoading(false);
513473
};
514474

515-
// Optimistic UI approach for task completion
475+
// FIXED: Improved task completion with better error handling and data consistency
516476
const handleMarkAsDone = async () => {
517477
if (!task) return;
518478

479+
// Validate required fields before attempting completion
480+
if (!task.id) {
481+
toast.error("Task ID is missing");
482+
return;
483+
}
484+
485+
if (!task.pr_link) {
486+
toast.error("PR Link is required to complete the task");
487+
return;
488+
}
489+
519490
setIsLoading(true);
520491

521-
// Optimistically remove the task from UI immediately
492+
console.log("Attempting to complete task with ID:", task.id);
493+
494+
// Optimistically remove the task from UI
522495
removeTaskOptimistic(task.id);
523496

524-
// Close modal immediately for better UX
497+
// Close modal for better UX
525498
onClose();
526499

527500
// Show optimistic success message
528501
toast.success("Completing task...");
529502

530503
try {
504+
// Pass the original task object - let the server action handle field extraction
531505
const result = await completeTask(task);
532506

507+
console.log("Task completion result:", result);
508+
533509
if (result.success) {
534-
// Update success message
535510
toast.success("Task completed and points awarded!");
536511

537-
// Call onComplete callback if provided
538512
if (onComplete) {
539513
onComplete(task.id);
540514
}
541515

542-
// Optional: Fetch fresh data in background after a delay
543-
// This ensures data consistency without blocking the UI
516+
// Refresh data in background
544517
setTimeout(() => {
545518
fetchBoardData();
546519
}, 1000);
547520
} else {
548-
// Revert optimistic update by refetching data
549-
toast.error(result.error || "Failed to complete task");
521+
// Revert optimistic update
522+
const errorMessage = result.error || "Failed to complete task";
523+
console.error("Task completion failed:", errorMessage, result);
524+
toast.error(errorMessage);
550525
await fetchBoardData();
551526
}
552527
} catch (error) {
553528
console.error("Error completing task:", error);
554-
toast.error("Failed to complete task");
555-
// Revert optimistic update by refetching data
529+
const errorMsg = error instanceof Error ? error.message : "Failed to complete task";
530+
toast.error(errorMsg);
556531
await fetchBoardData();
557532
} finally {
558533
setIsLoading(false);
@@ -564,12 +539,10 @@ const TaskViewModal = ({
564539

565540
const newAssigneeId = memberIds.length > 0 ? memberIds[0] : undefined;
566541

567-
// Set manual save state to show Save Changes button
568542
setManualSaveChanges(true);
569543
setPendingAssigneeId(newAssigneeId);
570544

571545
if (newAssigneeId) {
572-
// Fetch assignee data for UI display only
573546
const { data: assigneeData } = await supabase
574547
.from("codev")
575548
.select("id, first_name, last_name, image_url")
@@ -580,73 +553,58 @@ const TaskViewModal = ({
580553
setPrimaryAssignee(assigneeData);
581554
}
582555
} else {
583-
// Handle removal case
584556
setPrimaryAssignee(null);
585557
}
586558

587559
setForceRefreshKey(`${Date.now()}-${Math.random()}`);
588560
};
589561

590-
// Reset state when modal closes without saving
562+
// FIXED: Simplified close handler with cleaner state reset
591563
const handleClose = () => {
592564
if (hasUnsavedChanges) {
593-
// Reset all unsaved changes
594565
setManualSaveChanges(false);
595566
setPendingAssigneeId(undefined);
596567
setPrLink(originalPrLink);
597568

598-
// Force complete reset by setting primary assignee to null first
599-
setPrimaryAssignee(null);
600-
601-
// Then set a unique force refresh key to reset AssigneeSelector
602-
setForceRefreshKey(`close-reset-${Date.now()}-${Math.random()}`);
603-
604-
// Reset to original task assignee after a brief delay
605-
setTimeout(() => {
606-
if (task?.codev_id || task?.codev?.id) {
607-
const assigneeId = task.codev_id || task.codev?.id || "";
608-
if (supabase && assigneeId) {
609-
supabase
610-
.from("codev")
611-
.select("id, first_name, last_name, image_url")
612-
.eq("id", assigneeId)
613-
.single()
614-
.then(({ data, error }) => {
615-
if (!error && data) {
616-
setPrimaryAssignee(data as CodevMember);
617-
}
618-
});
619-
}
620-
} else {
621-
// Ensure it stays null for unassigned tasks
622-
setPrimaryAssignee(null);
569+
// Reset assignee to original state
570+
if (task?.codev_id || task?.codev?.id) {
571+
const assigneeId = task.codev_id || task.codev?.id || "";
572+
if (supabase && assigneeId) {
573+
supabase
574+
.from("codev")
575+
.select("id, first_name, last_name, image_url")
576+
.eq("id", assigneeId)
577+
.single()
578+
.then(({ data, error }: { data: any; error: any }) => {
579+
setPrimaryAssignee(!error && data ? (data as CodevMember) : null);
580+
setForceRefreshKey(`reset-${Date.now()}`);
581+
});
623582
}
624-
}, 50);
583+
} else {
584+
setPrimaryAssignee(null);
585+
setForceRefreshKey(`reset-${Date.now()}`);
586+
}
625587
}
626588
onClose();
627589
};
628590

629-
// Enhanced handleSaveChanges with assignee removal support
630591
const handleSaveChanges = async () => {
631592
if (!task || !supabase) return;
632593

633594
setIsSavingChanges(true);
634595

635596
try {
636-
// Save PR Link changes
637597
if (hasPrLinkChanges) {
638598
await handleUpdate();
639599
}
640600

641-
// Save assignee changes (including removal)
642601
if (manualSaveChanges) {
643602
const { error } = await supabase
644603
.from("tasks")
645-
.update({ codev_id: pendingAssigneeId || null }) // Convert undefined to null for database
604+
.update({ codev_id: pendingAssigneeId || null })
646605
.eq("id", task.id);
647606

648607
if (!error) {
649-
// Update task object after successful save
650608
if (task) {
651609
task.codev_id = pendingAssigneeId;
652610
if (pendingAssigneeId && primaryAssignee) {
@@ -836,18 +794,17 @@ const TaskViewModal = ({
836794
)}
837795
</div>
838796

839-
{/* deadline */}
840797
<div className="space-y-2">
841798
<Label className="text-sm font-medium">Deadline</Label>
842799
<div className="rounded-md bg-blue-50 p-2 text-sm font-medium text-blue-700 dark:bg-blue-900/20 dark:text-blue-300">
843800
{task?.deadline ? new Date(task.deadline).toLocaleString() : "Not Set"}
844801
</div>
845802
</div>
846803

804+
{/* FIXED: Primary Assignee - Now allows modification even when assigned */}
847805
<div className="space-y-2">
848806
<Label className="text-sm font-medium">Primary Assignee</Label>
849-
{canModifyTask && !primaryAssignee ? (
850-
// Only show AssigneeSelector for UNASSIGNED tasks
807+
{canModifyTask ? (
851808
<div className="space-y-2">
852809
{boardId ? (
853810
<AssigneeSelector
@@ -865,7 +822,6 @@ const TaskViewModal = ({
865822
)}
866823
</div>
867824
) : primaryAssignee ? (
868-
// Read-only display for ASSIGNED tasks
869825
<div className="flex items-center gap-2">
870826
{primaryAssignee.image_url ? (
871827
<Image
@@ -883,7 +839,6 @@ const TaskViewModal = ({
883839
</span>
884840
</div>
885841
) : (
886-
// Read-only display for users without modify permissions
887842
<div className="flex items-center gap-2 text-gray-500">
888843
<DefaultAvatar size={32} />
889844
<span>Unassigned</span>
@@ -937,7 +892,6 @@ const TaskViewModal = ({
937892
/>
938893
</div>
939894

940-
{/* created by - created at */}
941895
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
942896
<div className="space-y-2">
943897
<Label className="text-sm font-medium">Created By</Label>
@@ -972,16 +926,14 @@ const TaskViewModal = ({
972926
</div>
973927

974928
<DialogFooter className="mt-4 flex justify-end">
975-
{/*Previous style: flex gap-6 sm:justify-end */}
976-
{/* Copy URL button */}
977929
<Button
978930
onClick={() => {
979931
if (!task?.id) {
980932
toast.error("Cannot copy URL: Task ID is missing");
981933
return;
982934
}
983935

984-
const baseUrl = window.location.origin; // gets the website domain
936+
const baseUrl = window.location.origin;
985937
const url = task.ticket_code
986938
? `${baseUrl}/home/kanban/ticket/${task.ticket_code}`
987939
: new URL(window.location.href).toString();
@@ -1003,7 +955,6 @@ const TaskViewModal = ({
1003955
Copy URL
1004956
</Button>
1005957

1006-
{/* VIEWING MODE: Show Close when just viewing (no changes) */}
1007958
{!hasUnsavedChanges && (
1008959
<Button
1009960
onClick={onClose}
@@ -1022,7 +973,6 @@ const TaskViewModal = ({
1022973
</Button>
1023974
)}
1024975

1025-
{/* EDITING MODE: Show Cancel + Save Changes when has changes */}
1026976
{hasUnsavedChanges && (
1027977
<>
1028978
<Button
@@ -1063,7 +1013,6 @@ const TaskViewModal = ({
10631013
</>
10641014
)}
10651015

1066-
{/* Mark as Done button - separate logic */}
10671016
{canMarkAsDone && task?.pr_link && (
10681017
<Button
10691018
onClick={handleMarkAsDone}
@@ -1092,4 +1041,4 @@ const TaskViewModal = ({
10921041
);
10931042
};
10941043

1095-
export default TaskViewModal;
1044+
export default TaskViewModal;

0 commit comments

Comments
 (0)