diff --git a/app/api/twenty-crm/tasks/route.ts b/app/api/twenty-crm/tasks/route.ts index e879821..9282091 100644 --- a/app/api/twenty-crm/tasks/route.ts +++ b/app/api/twenty-crm/tasks/route.ts @@ -42,20 +42,11 @@ async function fetchTwentyTasks(): Promise { const todayISO = today.toISOString(); // GraphQL query to fetch tasks from Twenty CRM - // Based on error messages, using correct field names: - // - filter instead of where - // - bodyV2 instead of body - // - status field instead of completedAt (need to check what status values mean) - // - name instead of firstName/lastName/email for WorkspaceMember + // Trying simpler query first - tasks may not accept filter/orderBy arguments + // We'll filter client-side if needed const query = ` - query GetOverdueTasks { - tasks( - filter: { - status: { neq: Done } - dueAt: { lt: "${todayISO}" } - } - orderBy: { dueAt: AscNullsLast } - ) { + query GetTasks { + tasks { edges { node { id @@ -148,36 +139,62 @@ async function fetchTwentyTasks(): Promise { } // Transform Twenty CRM tasks to match our Task interface - const tasks: TwentyTask[] = activitiesData.map((edge: any) => { - const node = edge.node || edge; // Handle both edge.node and direct node - - // Extract text from bodyV2 if it's a rich text object - let bodyText = null; - if (node.bodyV2) { - if (typeof node.bodyV2 === 'string') { - bodyText = node.bodyV2; - } else if (node.bodyV2 && typeof node.bodyV2 === 'object') { - // Try to extract text from rich text structure - bodyText = JSON.stringify(node.bodyV2).substring(0, 200); + // Filter client-side for overdue tasks (dueAt < today) and not completed (status !== 'Done') + const tasks: TwentyTask[] = activitiesData + .map((edge: any) => { + const node = edge.node || edge; // Handle both edge.node and direct node + + // Extract text from bodyV2 if it's a rich text object + let bodyText = null; + if (node.bodyV2) { + if (typeof node.bodyV2 === 'string') { + bodyText = node.bodyV2; + } else if (node.bodyV2 && typeof node.bodyV2 === 'object') { + // Try to extract text from rich text structure + bodyText = JSON.stringify(node.bodyV2).substring(0, 200); + } } - } - - return { - id: node.id, - title: node.title || 'Untitled Task', - bodyV2: node.bodyV2 || null, - dueAt: node.dueAt || null, - status: node.status || null, - type: node.type || 'Task', - assigneeId: node.assigneeId || null, - assignee: node.assignee ? { - id: node.assignee.id, - name: node.assignee.name || null, - } : null, - // Store extracted body text for easier access - _bodyText: bodyText, - }; - }); + + return { + id: node.id, + title: node.title || 'Untitled Task', + bodyV2: node.bodyV2 || null, + dueAt: node.dueAt || null, + status: node.status || null, + type: node.type || 'Task', + assigneeId: node.assigneeId || null, + assignee: node.assignee ? { + id: node.assignee.id, + name: node.assignee.name || null, + } : null, + // Store extracted body text for easier access + _bodyText: bodyText, + }; + }) + .filter((task: TwentyTask) => { + // Filter: only overdue tasks (dueAt < today) and not completed + if (task.status === 'Done') { + return false; + } + + if (!task.dueAt) { + return false; // Exclude tasks without due date + } + + const taskDueDate = new Date(task.dueAt); + taskDueDate.setHours(0, 0, 0, 0); + const today = new Date(); + today.setHours(0, 0, 0, 0); + + return taskDueDate < today; // Only overdue tasks + }) + .sort((a: TwentyTask, b: TwentyTask) => { + // Sort by dueAt (oldest first) + if (!a.dueAt || !b.dueAt) return 0; + const dateA = new Date(a.dueAt).getTime(); + const dateB = new Date(b.dueAt).getTime(); + return dateA - dateB; + }); logger.debug('[TWENTY_CRM_TASKS] Successfully fetched tasks from Twenty CRM', { count: tasks.length,