From 8e6187e20581d56e22a705bb14ca7af443c921c4 Mon Sep 17 00:00:00 2001 From: Adinorio Date: Fri, 20 Jun 2025 18:25:34 +0800 Subject: [PATCH 1/4] fix(api): better filtering system with task status --- .../[wsId]/tasks/boards/[boardId]/task.tsx | 47 +++++++----- .../api/v1/workspaces/[wsId]/tasks/route.ts | 29 ++++++-- apps/web/src/lib/task-helper.ts | 73 ++++++++----------- apps/web/src/lib/time-tracking-helper.ts | 63 +++++++++++++++- packages/types/src/primitives/TaskBoard.ts | 3 + 5 files changed, 147 insertions(+), 68 deletions(-) diff --git a/apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx b/apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx index 45b029dda..e6ce5c9eb 100644 --- a/apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx +++ b/apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx @@ -58,6 +58,8 @@ import { isYesterday, } from 'date-fns'; import { useEffect, useRef, useState } from 'react'; +import { moveTask } from '@/lib/task-helper'; +import { toast } from '@tuturuuu/ui/hooks/use-toast'; export interface Task extends TaskType {} @@ -67,6 +69,7 @@ interface Props { taskList?: TaskList; isOverlay?: boolean; onUpdate?: () => void; + availableLists?: TaskList[]; // Optional: pass from parent to avoid redundant API calls } export function TaskCard({ @@ -75,6 +78,7 @@ export function TaskCard({ taskList, isOverlay, onUpdate, + availableLists: propAvailableLists, }: Props) { const [isLoading, setIsLoading] = useState(false); const [isHovered, setIsHovered] = useState(false); @@ -91,8 +95,13 @@ export function TaskCard({ 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) useEffect(() => { + if (propAvailableLists) { + setAvailableLists(propAvailableLists); + return; + } + const fetchTaskLists = async () => { const supabase = createClient(); const { data, error } = await supabase @@ -109,7 +118,7 @@ export function TaskCard({ }; fetchTaskLists(); - }, [boardId]); + }, [boardId, propAvailableLists]); // Find the first list with 'done' or 'closed' status const getTargetCompletionList = () => { @@ -244,22 +253,24 @@ export function TaskCard({ 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); + } } async function handleDelete() { diff --git a/apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts b/apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts index 0b2026855..646bfab0c 100644 --- a/apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts +++ b/apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts @@ -14,6 +14,7 @@ interface TaskAssigneeData { interface TaskListData { id: string; name: string | null; + status: string | null; workspace_boards: { id: string; name: string | null; @@ -21,7 +22,7 @@ interface TaskListData { } | null; } -interface RawTaskData { +interface TaskData { id: string; name: string; description: string | null; @@ -31,6 +32,7 @@ interface RawTaskData { end_date: string | null; created_at: string | null; list_id: string; + archived: boolean | null; task_lists: TaskListData | null; assignees?: TaskAssigneeData[]; } @@ -86,6 +88,9 @@ export async function GET( Number.isFinite(parsedOffset) && parsedOffset >= 0 ? parsedOffset : 0; 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; // Build the query for fetching tasks with assignee information let query = supabase @@ -101,9 +106,11 @@ export async function GET( end_date, created_at, list_id, + archived, task_lists!inner ( id, name, + status, board_id, workspace_boards!inner ( id, @@ -123,6 +130,14 @@ export async function GET( .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 + } + // Apply filters based on query parameters if (listId) { query = query.eq('list_id', listId); @@ -142,7 +157,7 @@ export async function GET( // Transform the data to match the expected WorkspaceTask format const tasks = - data?.map((task: RawTaskData) => ({ + data?.map((task: TaskData) => ({ id: task.id, name: task.name, description: task.description, @@ -152,20 +167,22 @@ export async function GET( end_date: task.end_date, created_at: task.created_at, list_id: task.list_id, + archived: task.archived, // 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, // Add assignee information assignees: [ ...(task.assignees ?? []) - .map((a) => a.user) - .filter((u): u is NonNullable => !!u?.id) - .reduce((uniqueUsers, user) => { + .map((a: any) => a.user) + .filter((u: any) => !!u?.id) + .reduce((uniqueUsers: Map, user: any) => { if (!uniqueUsers.has(user.id)) { uniqueUsers.set(user.id, user); } return uniqueUsers; - }, new Map[0]['user']>()) + }, new Map()) .values(), ], // Add helper field to identify if current user is assigned diff --git a/apps/web/src/lib/task-helper.ts b/apps/web/src/lib/task-helper.ts index 846bca4f2..b9524f315 100644 --- a/apps/web/src/lib/task-helper.ts +++ b/apps/web/src/lib/task-helper.ts @@ -143,13 +143,33 @@ export async function updateTask( 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 && + user.id && + self.findIndex((u: any) => u.id === user.id) === index + ) || []; +} + +// 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] }); + } + // Always invalidate time tracker since task availability affects it + queryClient.invalidateQueries({ queryKey: ['time-tracking-data'] }); +} + 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') @@ -162,17 +182,12 @@ export async function moveTask( 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({ @@ -199,23 +214,10 @@ export async function moveTask( 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), }; return transformedTask as Task; @@ -484,17 +486,10 @@ export function useMoveTask(boardId: string) { 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] }); @@ -514,9 +509,7 @@ export function useMoveTask(boardId: string) { return { previousTasks }; }, - onError: (err, variables, context) => { - console.error('❌ useMoveTask onError:', err, variables); - + onError: (err, _variables, context) => { // Rollback optimistic update on error if (context?.previousTasks) { queryClient.setQueryData(['tasks', boardId], context.previousTasks); @@ -529,9 +522,7 @@ export function useMoveTask(boardId: string) { variant: 'destructive', }); }, - onSuccess: (updatedTask, variables) => { - console.log('✅ useMoveTask onSuccess:', { updatedTask, variables }); - + onSuccess: (updatedTask, _variables) => { // Update the cache with the server response queryClient.setQueryData( ['tasks', boardId], @@ -543,11 +534,9 @@ export function useMoveTask(boardId: string) { } ); }, - onSettled: (data, error, variables) => { - console.log('🏁 useMoveTask onSettled:', { data, error, variables }); - - // Ensure data consistency - queryClient.invalidateQueries({ queryKey: ['tasks', boardId] }); + onSettled: (_data, _error, _variables) => { + // Ensure data consistency across all task-related caches + invalidateTaskCaches(queryClient, boardId); }, }); } diff --git a/apps/web/src/lib/time-tracking-helper.ts b/apps/web/src/lib/time-tracking-helper.ts index a306a2217..087ac9827 100644 --- a/apps/web/src/lib/time-tracking-helper.ts +++ b/apps/web/src/lib/time-tracking-helper.ts @@ -48,7 +48,36 @@ export const getTimeTrackingData = async (wsId: string, userId: string) => { .eq('ws_id', wsId) .eq('user_id', userId); - const tasksPromise = sbAdmin.from('tasks').select('*').limit(100); + const tasksPromise = sbAdmin + .from('tasks') + .select(` + *, + list:task_lists!inner( + id, + name, + status, + board:workspace_boards!inner( + id, + name, + ws_id + ) + ), + assignees:task_assignees( + user:users( + id, + display_name, + avatar_url, + user_private_details(email) + ) + ) + `) + .eq('list.board.ws_id', wsId) + .eq('deleted', false) + .eq('archived', false) + .in('list.status', ['not_started', 'active']) // Only include tasks from not_started and active lists + .eq('list.deleted', false) + .order('created_at', { ascending: false }) + .limit(100); // Stats are more complex and require processing, which we'll do after fetching const allSessionsPromise = sbAdmin @@ -190,12 +219,42 @@ export const getTimeTrackingData = async (wsId: string, userId: string) => { dailyActivity, }; + // Transform tasks to match the ExtendedWorkspaceTask interface expected by the time tracker + const transformedTasks = (tasks || []).map((task: any) => ({ + ...task, + // Flatten nested data for easier access + board_id: task.list?.board?.id, + board_name: task.list?.board?.name, + list_id: task.list?.id, + list_name: task.list?.name, + list_status: task.list?.status, + // Transform assignees to match expected format + assignees: task.assignees + ?.map((a: any) => ({ + ...a.user, + // Extract email from nested user_private_details + email: a.user?.user_private_details?.[0]?.email || null, + })) + .filter( + (user: any, index: number, self: any[]) => + user && + user.id && + self.findIndex((u: any) => u.id === user.id) === index + ) || [], + // Add current user assignment flag + is_assigned_to_current_user: task.assignees?.some( + (a: any) => a.user?.id === userId + ) || false, + // Ensure task is available for time tracking + completed: false, // Since we filtered out archived tasks, none should be completed + })); + return { categories: categories || [], runningSession: runningSession || null, recentSessions: recentSessions || [], goals: goals || [], - tasks: tasks || [], + tasks: transformedTasks, stats, }; }; diff --git a/packages/types/src/primitives/TaskBoard.ts b/packages/types/src/primitives/TaskBoard.ts index 93d998921..d9b50fad2 100644 --- a/packages/types/src/primitives/TaskBoard.ts +++ b/packages/types/src/primitives/TaskBoard.ts @@ -1,5 +1,8 @@ import { SupportedColor } from './SupportedColors'; +// Task Board Status - Used for organizing tasks into workflow stages +// NOTE: This is different from AI module TaskStatus which has different values +// Do not confuse with: 'not-started', 'in-progress', 'completed', 'blocked' export type TaskBoardStatus = 'not_started' | 'active' | 'done' | 'closed'; export interface TaskBoardStatusTemplate { From a770084d47a06e7359a7fc8cfd6056fe379f98ba Mon Sep 17 00:00:00 2001 From: Adinorio Date: Fri, 20 Jun 2025 19:06:47 +0800 Subject: [PATCH 2/4] fix(tasks): prevent completed tasks from appearing in time tracker - Filter time tracker API to exclude tasks from 'done' and 'closed' lists - Update task movement logic to properly archive completed tasks - Add proper cache invalidation for time tracker when tasks move - Standardize task completion logic across components - Refactor duplicate assignee transformation into shared utility - Add documentation for task list status values - Improve type safety with proper interface definitions BREAKING CHANGE: Time tracker will no longer show tasks from completed lists Resolves task status logic inconsistency where done tasks remained trackable --- .../api/v1/workspaces/[wsId]/tasks/route.ts | 2 ++ apps/web/src/lib/task-helper.ts | 11 +++++------ apps/web/src/lib/time-tracking-helper.ts | 18 ++++++------------ 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts b/apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts index 646bfab0c..f5ea1850e 100644 --- a/apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts +++ b/apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts @@ -14,6 +14,8 @@ interface TaskAssigneeData { 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; workspace_boards: { id: string; diff --git a/apps/web/src/lib/task-helper.ts b/apps/web/src/lib/task-helper.ts index b9524f315..58672aa7d 100644 --- a/apps/web/src/lib/task-helper.ts +++ b/apps/web/src/lib/task-helper.ts @@ -147,12 +147,11 @@ export async function updateTask( export function transformAssignees(assignees: any[]): any[] { return 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 - ) || []; + .filter( + (user: any, index: number, self: any[]) => + user?.id && + self.findIndex((u: any) => u.id === user.id) === index + ) || []; } // Utility function to invalidate all task-related caches consistently diff --git a/apps/web/src/lib/time-tracking-helper.ts b/apps/web/src/lib/time-tracking-helper.ts index 087ac9827..653a5ba97 100644 --- a/apps/web/src/lib/time-tracking-helper.ts +++ b/apps/web/src/lib/time-tracking-helper.ts @@ -4,6 +4,7 @@ import { createAdminClient, createClient, } from '@tuturuuu/supabase/next/server'; +import { transformAssignees } from '@/lib/task-helper'; import 'server-only'; export const getTimeTrackingData = async (wsId: string, userId: string) => { @@ -229,18 +230,11 @@ export const getTimeTrackingData = async (wsId: string, userId: string) => { list_name: task.list?.name, list_status: task.list?.status, // Transform assignees to match expected format - assignees: task.assignees - ?.map((a: any) => ({ - ...a.user, - // Extract email from nested user_private_details - email: a.user?.user_private_details?.[0]?.email || null, - })) - .filter( - (user: any, index: number, self: any[]) => - user && - user.id && - self.findIndex((u: any) => u.id === user.id) === index - ) || [], + assignees: transformAssignees(task.assignees || []).map((user: any) => ({ + ...user, + // Extract email from nested user_private_details + email: user?.user_private_details?.[0]?.email || null, + })), // Add current user assignment flag is_assigned_to_current_user: task.assignees?.some( (a: any) => a.user?.id === userId From 9ee4aa57ca0b38d24cf3253436d8ba36d684fcb2 Mon Sep 17 00:00:00 2001 From: Adinorio Date: Fri, 20 Jun 2025 19:15:21 +0800 Subject: [PATCH 3/4] refactor(tasks): improve type safety in assignee processing - Add ProcessedAssignee interface for better type definitions - Replace 'any' types with proper TypeScript interfaces - Use type guards for safer runtime type checking - Enhance assignee deduplication with strong typing - Improve code maintainability and IDE support The API now has comprehensive type safety for assignee data processing while maintaining backward compatibility with existing functionality. --- .../src/app/api/v1/workspaces/[wsId]/tasks/route.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts b/apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts index f5ea1850e..697e6e5ef 100644 --- a/apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts +++ b/apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts @@ -2,6 +2,12 @@ import { createClient } from '@tuturuuu/supabase/next/server'; import { NextRequest, NextResponse } from 'next/server'; // Type interfaces for better type safety +interface ProcessedAssignee { + id: string; + display_name: string | null; + avatar_url: string | null; +} + interface TaskAssigneeData { user: { id: string; @@ -177,9 +183,9 @@ export async function GET( // Add assignee information assignees: [ ...(task.assignees ?? []) - .map((a: any) => a.user) - .filter((u: any) => !!u?.id) - .reduce((uniqueUsers: Map, user: any) => { + .map((a: TaskAssigneeData) => a.user) + .filter((u): u is ProcessedAssignee => !!u?.id) + .reduce((uniqueUsers: Map, user: ProcessedAssignee) => { if (!uniqueUsers.has(user.id)) { uniqueUsers.set(user.id, user); } From e2de2862eca8a8653bfe17829bc1460140946f0f Mon Sep 17 00:00:00 2001 From: Adinorio <156925721+Adinorio@users.noreply.github.com> Date: Fri, 20 Jun 2025 11:18:39 +0000 Subject: [PATCH 4/4] style: apply prettier formatting --- .../[wsId]/tasks/boards/[boardId]/task.tsx | 6 +++--- .../api/v1/workspaces/[wsId]/tasks/route.ts | 20 ++++++++++++------- apps/web/src/lib/task-helper.ts | 13 ++++++------ apps/web/src/lib/time-tracking-helper.ts | 15 +++++++------- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx b/apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx index e6ce5c9eb..8d863d224 100644 --- a/apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx +++ b/apps/web/src/app/[locale]/(dashboard)/[wsId]/tasks/boards/[boardId]/task.tsx @@ -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'; import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { createClient } from '@tuturuuu/supabase/next/client'; @@ -29,6 +30,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from '@tuturuuu/ui/dropdown-menu'; +import { toast } from '@tuturuuu/ui/hooks/use-toast'; import { AlertCircle, Calendar, @@ -58,8 +60,6 @@ import { isYesterday, } from 'date-fns'; import { useEffect, useRef, useState } from 'react'; -import { moveTask } from '@/lib/task-helper'; -import { toast } from '@tuturuuu/ui/hooks/use-toast'; export interface Task extends TaskType {} @@ -253,7 +253,7 @@ export function TaskCard({ if (!targetCompletionList || !onUpdate) return; setIsLoading(true); - + // Use the standard moveTask function to ensure consistent logic const supabase = createClient(); try { diff --git a/apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts b/apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts index 697e6e5ef..3f7f86ff0 100644 --- a/apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts +++ b/apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts @@ -96,7 +96,7 @@ export async function GET( Number.isFinite(parsedOffset) && parsedOffset >= 0 ? parsedOffset : 0; 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; @@ -185,12 +185,18 @@ export async function GET( ...(task.assignees ?? []) .map((a: TaskAssigneeData) => a.user) .filter((u): u is ProcessedAssignee => !!u?.id) - .reduce((uniqueUsers: Map, user: ProcessedAssignee) => { - if (!uniqueUsers.has(user.id)) { - uniqueUsers.set(user.id, user); - } - return uniqueUsers; - }, new Map()) + .reduce( + ( + uniqueUsers: Map, + user: ProcessedAssignee + ) => { + if (!uniqueUsers.has(user.id)) { + uniqueUsers.set(user.id, user); + } + return uniqueUsers; + }, + new Map() + ) .values(), ], // Add helper field to identify if current user is assigned diff --git a/apps/web/src/lib/task-helper.ts b/apps/web/src/lib/task-helper.ts index 58672aa7d..920697aad 100644 --- a/apps/web/src/lib/task-helper.ts +++ b/apps/web/src/lib/task-helper.ts @@ -145,13 +145,14 @@ export async function updateTask( // Utility function to transform and deduplicate assignees export function transformAssignees(assignees: any[]): any[] { - return assignees - ?.map((a: any) => a.user) - .filter( + 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 - ) || []; + user?.id && self.findIndex((u: any) => u.id === user.id) === index + ) || [] + ); } // Utility function to invalidate all task-related caches consistently diff --git a/apps/web/src/lib/time-tracking-helper.ts b/apps/web/src/lib/time-tracking-helper.ts index 653a5ba97..c092db29b 100644 --- a/apps/web/src/lib/time-tracking-helper.ts +++ b/apps/web/src/lib/time-tracking-helper.ts @@ -1,10 +1,10 @@ 'use server'; +import { transformAssignees } from '@/lib/task-helper'; import { createAdminClient, createClient, } from '@tuturuuu/supabase/next/server'; -import { transformAssignees } from '@/lib/task-helper'; import 'server-only'; export const getTimeTrackingData = async (wsId: string, userId: string) => { @@ -51,7 +51,8 @@ export const getTimeTrackingData = async (wsId: string, userId: string) => { const tasksPromise = sbAdmin .from('tasks') - .select(` + .select( + ` *, list:task_lists!inner( id, @@ -71,7 +72,8 @@ export const getTimeTrackingData = async (wsId: string, userId: string) => { user_private_details(email) ) ) - `) + ` + ) .eq('list.board.ws_id', wsId) .eq('deleted', false) .eq('archived', false) @@ -232,13 +234,12 @@ export const getTimeTrackingData = async (wsId: string, userId: string) => { // Transform assignees to match expected format assignees: transformAssignees(task.assignees || []).map((user: any) => ({ ...user, - // Extract email from nested user_private_details + // Extract email from nested user_private_details email: user?.user_private_details?.[0]?.email || null, })), // Add current user assignment flag - is_assigned_to_current_user: task.assignees?.some( - (a: any) => a.user?.id === userId - ) || false, + is_assigned_to_current_user: + task.assignees?.some((a: any) => a.user?.id === userId) || false, // Ensure task is available for time tracking completed: false, // Since we filtered out archived tasks, none should be completed }));