Skip to content

Improvement of Task filtering status #3140

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 20, 2025
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,6 +1,7 @@
import { AssigneeSelect } from './_components/assignee-select';
import { TaskActions } from './task-actions';
import { useDeleteTask, useUpdateTask } from '@/lib/task-helper';
import { moveTask } from '@/lib/task-helper';

Check warning on line 4 in apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx#L4

Added line #L4 was not covered by tests
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { createClient } from '@tuturuuu/supabase/next/client';
Expand Down Expand Up @@ -29,6 +30,7 @@
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@tuturuuu/ui/dropdown-menu';
import { toast } from '@tuturuuu/ui/hooks/use-toast';

Check warning on line 33 in apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx#L33

Added line #L33 was not covered by tests
import {
AlertCircle,
Calendar,
Expand Down Expand Up @@ -67,6 +69,7 @@
taskList?: TaskList;
isOverlay?: boolean;
onUpdate?: () => void;
availableLists?: TaskList[]; // Optional: pass from parent to avoid redundant API calls

Check warning on line 72 in apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx#L72

Added line #L72 was not covered by tests
}

export function TaskCard({
Expand All @@ -75,6 +78,7 @@
taskList,
isOverlay,
onUpdate,
availableLists: propAvailableLists,

Check warning on line 81 in apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx#L81

Added line #L81 was not covered by tests
}: Props) {
const [isLoading, setIsLoading] = useState(false);
const [isHovered, setIsHovered] = useState(false);
Expand All @@ -91,8 +95,13 @@
const updateTaskMutation = useUpdateTask(boardId);
const deleteTaskMutation = useDeleteTask(boardId);

// Fetch available task lists for the board
// Fetch available task lists for the board (only if not provided as prop)

Check warning on line 98 in apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx#L98

Added line #L98 was not covered by tests
useEffect(() => {
if (propAvailableLists) {
setAvailableLists(propAvailableLists);
return;
}

Check warning on line 104 in apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx#L100-L104

Added lines #L100 - L104 were not covered by tests
const fetchTaskLists = async () => {
const supabase = createClient();
const { data, error } = await supabase
Expand All @@ -109,7 +118,7 @@
};

fetchTaskLists();
}, [boardId]);
}, [boardId, propAvailableLists]);

Check warning on line 121 in apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx#L121

Added line #L121 was not covered by tests

// Find the first list with 'done' or 'closed' status
const getTargetCompletionList = () => {
Expand Down Expand Up @@ -244,22 +253,24 @@
if (!targetCompletionList || !onUpdate) return;

setIsLoading(true);
updateTaskMutation.mutate(
{
taskId: task.id,
updates: {
list_id: targetCompletionList.id,
archived: true, // Also mark as archived when moving to completion
},
},
{
onSettled: () => {
setIsLoading(false);
setMenuOpen(false);
onUpdate();
},
}
);

// Use the standard moveTask function to ensure consistent logic
const supabase = createClient();
try {
await moveTask(supabase, task.id, targetCompletionList.id);
// Manually invalidate queries since we're not using the mutation hook
onUpdate();
} catch (error) {
console.error('Failed to move task to completion:', error);
toast({
title: 'Error',
description: 'Failed to complete task. Please try again.',
variant: 'destructive',
});
} finally {
setIsLoading(false);
setMenuOpen(false);
}

Check warning on line 273 in apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx#L256-L273

Added lines #L256 - L273 were not covered by tests
}

async function handleDelete() {
Expand Down
51 changes: 41 additions & 10 deletions apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
import { NextRequest, NextResponse } from 'next/server';

// Type interfaces for better type safety
interface ProcessedAssignee {
id: string;
display_name: string | null;
avatar_url: string | null;
}

Check warning on line 10 in apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts#L5-L10

Added lines #L5 - L10 were not covered by tests
interface TaskAssigneeData {
user: {
id: string;
Expand All @@ -14,14 +20,17 @@
interface TaskListData {
id: string;
name: string | null;
// Task list status: 'not_started' | 'active' | 'done' | 'closed'
// Used to determine task availability for time tracking
status: string | null;

Check warning on line 25 in apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts#L23-L25

Added lines #L23 - L25 were not covered by tests
workspace_boards: {
id: string;
name: string | null;
ws_id: string;
} | null;
}

interface RawTaskData {
interface TaskData {

Check warning on line 33 in apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts#L33

Added line #L33 was not covered by tests
id: string;
name: string;
description: string | null;
Expand All @@ -31,6 +40,7 @@
end_date: string | null;
created_at: string | null;
list_id: string;
archived: boolean | null;

Check warning on line 43 in apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts#L43

Added line #L43 was not covered by tests
task_lists: TaskListData | null;
assignees?: TaskAssigneeData[];
}
Expand Down Expand Up @@ -87,6 +97,9 @@
const boardId = url.searchParams.get('boardId');
const listId = url.searchParams.get('listId');

// Check if this is a request for time tracking (indicated by limit=100 and no specific filters)
const isTimeTrackingRequest = limit === 100 && !boardId && !listId;

Check warning on line 102 in apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts#L100-L102

Added lines #L100 - L102 were not covered by tests
// Build the query for fetching tasks with assignee information
let query = supabase
.from('tasks')
Expand All @@ -101,9 +114,11 @@
end_date,
created_at,
list_id,
archived,

Check warning on line 117 in apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts#L117

Added line #L117 was not covered by tests
task_lists!inner (
id,
name,
status,

Check warning on line 121 in apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts#L121

Added line #L121 was not covered by tests
board_id,
workspace_boards!inner (
id,
Expand All @@ -123,6 +138,14 @@
.eq('task_lists.workspace_boards.ws_id', wsId)
.eq('deleted', false);

// IMPORTANT: If this is for time tracking, apply the same filters as the server-side helper
if (isTimeTrackingRequest) {
query = query
.eq('archived', false) // Only non-archived tasks
.in('task_lists.status', ['not_started', 'active']) // Only from active lists
.eq('task_lists.deleted', false); // Ensure list is not deleted
}

Check warning on line 148 in apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts#L141-L148

Added lines #L141 - L148 were not covered by tests
// Apply filters based on query parameters
if (listId) {
query = query.eq('list_id', listId);
Expand All @@ -142,7 +165,7 @@

// Transform the data to match the expected WorkspaceTask format
const tasks =
data?.map((task: RawTaskData) => ({
data?.map((task: TaskData) => ({

Check warning on line 168 in apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts#L168

Added line #L168 was not covered by tests
id: task.id,
name: task.name,
description: task.description,
Expand All @@ -152,20 +175,28 @@
end_date: task.end_date,
created_at: task.created_at,
list_id: task.list_id,
archived: task.archived,

Check warning on line 178 in apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts#L178

Added line #L178 was not covered by tests
// Add board information for context
board_name: task.task_lists?.workspace_boards?.name,
list_name: task.task_lists?.name,
list_status: task.task_lists?.status,

Check warning on line 182 in apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts#L182

Added line #L182 was not covered by tests
// Add assignee information
assignees: [
...(task.assignees ?? [])
.map((a) => a.user)
.filter((u): u is NonNullable<typeof u> => !!u?.id)
.reduce((uniqueUsers, user) => {
if (!uniqueUsers.has(user.id)) {
uniqueUsers.set(user.id, user);
}
return uniqueUsers;
}, new Map<string, NonNullable<typeof task.assignees>[0]['user']>())
.map((a: TaskAssigneeData) => a.user)
.filter((u): u is ProcessedAssignee => !!u?.id)
.reduce(
(
uniqueUsers: Map<string, ProcessedAssignee>,
user: ProcessedAssignee
) => {
if (!uniqueUsers.has(user.id)) {
uniqueUsers.set(user.id, user);
}
return uniqueUsers;
},
new Map()
)

Check warning on line 199 in apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts#L186-L199

Added lines #L186 - L199 were not covered by tests
.values(),
],
// Add helper field to identify if current user is assigned
Expand Down
73 changes: 31 additions & 42 deletions apps/web/src/lib/task-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,13 +143,33 @@
return data as Task;
}

// Utility function to transform and deduplicate assignees
export function transformAssignees(assignees: any[]): any[] {
return (
assignees
?.map((a: any) => a.user)
.filter(
(user: any, index: number, self: any[]) =>
user?.id && self.findIndex((u: any) => u.id === user.id) === index
) || []

Check warning on line 154 in apps/web/src/lib/task-helper.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/lib/task-helper.ts#L147-L154

Added lines #L147 - L154 were not covered by tests
);
}

Check warning on line 156 in apps/web/src/lib/task-helper.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/lib/task-helper.ts#L156

Added line #L156 was not covered by tests

// Utility function to invalidate all task-related caches consistently
export function invalidateTaskCaches(queryClient: any, boardId?: string) {
if (boardId) {
queryClient.invalidateQueries({ queryKey: ['tasks', boardId] });
queryClient.invalidateQueries({ queryKey: ['task_lists', boardId] });
}

Check warning on line 163 in apps/web/src/lib/task-helper.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/lib/task-helper.ts#L159-L163

Added lines #L159 - L163 were not covered by tests
// Always invalidate time tracker since task availability affects it
queryClient.invalidateQueries({ queryKey: ['time-tracking-data'] });
}

Check warning on line 166 in apps/web/src/lib/task-helper.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/lib/task-helper.ts#L165-L166

Added lines #L165 - L166 were not covered by tests

export async function moveTask(
supabase: SupabaseClient,
taskId: string,
newListId: string
) {
console.log('🔄 Starting moveTask:', { taskId, newListId });

// First, get the target list to check its status
const { data: targetList, error: listError } = await supabase
.from('task_lists')
Expand All @@ -162,17 +182,12 @@
throw listError;
}

console.log('✅ Target list found:', targetList);

// Determine if task should be marked as archived based on list status
// Determine task completion status based on list status
// - not_started, active: task is available for work (archived = false)
// - done, closed: task is completed/archived (archived = true)
const shouldArchive =
targetList.status === 'done' || targetList.status === 'closed';

console.log('📝 Task completion status will be:', {
shouldArchive,
reason: `List status is "${targetList.status}"`,
});

const { data, error } = await supabase
.from('tasks')
.update({
Expand All @@ -199,23 +214,10 @@
throw error;
}

console.log('✅ Task moved successfully in database:', {
taskId: data.id,
newListId: data.list_id,
archived: data.archived,
});

// Transform the nested assignees data
const transformedTask = {
...data,
assignees: data.assignees
?.map((a: any) => a.user)
.filter(
(user: any, index: number, self: any[]) =>
user &&
user.id &&
self.findIndex((u: any) => u.id === user.id) === index
),
assignees: transformAssignees(data.assignees),

Check warning on line 220 in apps/web/src/lib/task-helper.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/lib/task-helper.ts#L220

Added line #L220 was not covered by tests
};

return transformedTask as Task;
Expand Down Expand Up @@ -484,17 +486,10 @@
taskId: string;
newListId: string;
}) => {
console.log('🚀 useMoveTask mutation called:', {
taskId,
newListId,
boardId,
});
const supabase = createClient();
return moveTask(supabase, taskId, newListId);
},
onMutate: async ({ taskId, newListId }) => {
console.log('🔄 useMoveTask onMutate:', { taskId, newListId });

// Cancel any outgoing refetches
await queryClient.cancelQueries({ queryKey: ['tasks', boardId] });

Expand All @@ -514,9 +509,7 @@

return { previousTasks };
},
onError: (err, variables, context) => {
console.error('❌ useMoveTask onError:', err, variables);

onError: (err, _variables, context) => {

Check warning on line 512 in apps/web/src/lib/task-helper.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/lib/task-helper.ts#L512

Added line #L512 was not covered by tests
// Rollback optimistic update on error
if (context?.previousTasks) {
queryClient.setQueryData(['tasks', boardId], context.previousTasks);
Expand All @@ -529,9 +522,7 @@
variant: 'destructive',
});
},
onSuccess: (updatedTask, variables) => {
console.log('✅ useMoveTask onSuccess:', { updatedTask, variables });

onSuccess: (updatedTask, _variables) => {

Check warning on line 525 in apps/web/src/lib/task-helper.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/lib/task-helper.ts#L525

Added line #L525 was not covered by tests
// Update the cache with the server response
queryClient.setQueryData(
['tasks', boardId],
Expand All @@ -543,11 +534,9 @@
}
);
},
onSettled: (data, error, variables) => {
console.log('🏁 useMoveTask onSettled:', { data, error, variables });

// Ensure data consistency
queryClient.invalidateQueries({ queryKey: ['tasks', boardId] });
onSettled: (_data, _error, _variables) => {

Check warning on line 537 in apps/web/src/lib/task-helper.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/lib/task-helper.ts#L537

Added line #L537 was not covered by tests
// Ensure data consistency across all task-related caches
invalidateTaskCaches(queryClient, boardId);

Check warning on line 539 in apps/web/src/lib/task-helper.ts

View check run for this annotation

Codecov / codecov/patch

apps/web/src/lib/task-helper.ts#L539

Added line #L539 was not covered by tests
},
});
}
Expand Down
Loading