From 334bfd04e31a09f588f04ae4c253df4f5b865ef0 Mon Sep 17 00:00:00 2001 From: alma Date: Thu, 15 Jan 2026 22:46:31 +0100 Subject: [PATCH] widget leantime refactor --- app/api/twenty-crm/tasks/route.ts | 106 +++++++++++++++++++++++------- 1 file changed, 84 insertions(+), 22 deletions(-) diff --git a/app/api/twenty-crm/tasks/route.ts b/app/api/twenty-crm/tasks/route.ts index 8a8ac3b..30e70a0 100644 --- a/app/api/twenty-crm/tasks/route.ts +++ b/app/api/twenty-crm/tasks/route.ts @@ -120,57 +120,82 @@ async function fetchTwentyTasks(): Promise { return []; } + // Log raw response for debugging + logger.debug('[TWENTY_CRM_TASKS] Raw GraphQL response', { + hasData: !!data.data, + dataKeys: data.data ? Object.keys(data.data) : [], + tasksEdgesCount: data.data?.tasks?.edges?.length || 0, + sampleResponse: JSON.stringify(data.data).substring(0, 500), + }); + // Try different possible response structures // Twenty CRM may use different field names depending on version let activitiesData = null; if (data.data?.tasks?.edges) { activitiesData = data.data.tasks.edges; + logger.debug('[TWENTY_CRM_TASKS] Found tasks.edges', { count: activitiesData.length }); } else if (data.data?.activities?.edges) { activitiesData = data.data.activities.edges; + logger.debug('[TWENTY_CRM_TASKS] Found activities.edges', { count: activitiesData.length }); } else if (data.data?.findManyTasks?.edges) { activitiesData = data.data.findManyTasks.edges; + logger.debug('[TWENTY_CRM_TASKS] Found findManyTasks.edges', { count: activitiesData.length }); } else if (data.data?.findManyActivities?.edges) { activitiesData = data.data.findManyActivities.edges; + logger.debug('[TWENTY_CRM_TASKS] Found findManyActivities.edges', { count: activitiesData.length }); } else if (Array.isArray(data.data?.tasks)) { // Direct array response activitiesData = data.data.tasks.map((node: any) => ({ node })); + logger.debug('[TWENTY_CRM_TASKS] Found tasks as array', { count: activitiesData.length }); } else if (Array.isArray(data.data?.activities)) { // Direct array response activitiesData = data.data.activities.map((node: any) => ({ node })); + logger.debug('[TWENTY_CRM_TASKS] Found activities as array', { count: activitiesData.length }); } else if (Array.isArray(data.data)) { // Root level array activitiesData = data.data.map((node: any) => ({ node })); + logger.debug('[TWENTY_CRM_TASKS] Found data as array', { count: activitiesData.length }); } if (!activitiesData) { logger.warn('[TWENTY_CRM_TASKS] Unexpected response format from Twenty CRM', { dataKeys: Object.keys(data.data || {}), - fullData: JSON.stringify(data.data).substring(0, 1000), + fullData: JSON.stringify(data.data).substring(0, 2000), }); return []; } + logger.debug('[TWENTY_CRM_TASKS] Activities data extracted', { + count: activitiesData.length, + sample: activitiesData.length > 0 ? { + firstTask: { + id: activitiesData[0]?.node?.id || activitiesData[0]?.id, + title: activitiesData[0]?.node?.title || activitiesData[0]?.title, + status: activitiesData[0]?.node?.status || activitiesData[0]?.status, + dueAt: activitiesData[0]?.node?.dueAt || activitiesData[0]?.dueAt, + } + } : null, + }); + // Transform Twenty CRM tasks to match our Task interface - // 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 - + const allTasks: TwentyTask[] = activitiesData.map((edge: any) => { + const node = edge.node || edge; // Handle both edge.node and direct node + // Extract text from bodyV2 (RichTextV2 type) let bodyText = null; if (node.bodyV2) { // bodyV2 has blocknote and markdown subfields bodyText = node.bodyV2.markdown || node.bodyV2.blocknote || null; } - - 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, + + 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 ? { @@ -178,26 +203,53 @@ async function fetchTwentyTasks(): Promise { lastName: node.assignee.name.lastName || null, } : null, } : null, - // Store extracted body text for easier access - _bodyText: bodyText, - }; - }) + // Store extracted body text for easier access + _bodyText: bodyText, + }; + }); + + // Log all tasks before filtering for debugging + logger.debug('[TWENTY_CRM_TASKS] All tasks before filtering', { + count: allTasks.length, + tasks: allTasks.map(t => ({ + id: t.id, + title: t.title, + status: t.status, + dueAt: t.dueAt, + hasDueDate: !!t.dueAt, + })), + }); + + // Filter client-side for overdue tasks (dueAt < today) and not completed (status !== 'Done') + const today = new Date(); + today.setHours(0, 0, 0, 0); + + const tasks: TwentyTask[] = allTasks .filter((task: TwentyTask) => { // Filter: only overdue tasks (dueAt < today) and not completed if (task.status === 'Done') { + logger.debug('[TWENTY_CRM_TASKS] Filtering out Done task', { id: task.id, status: task.status }); return false; } if (!task.dueAt) { + logger.debug('[TWENTY_CRM_TASKS] Filtering out task without due date', { id: task.id }); 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 + const isOverdue = taskDueDate < today; + logger.debug('[TWENTY_CRM_TASKS] Task date check', { + id: task.id, + dueAt: task.dueAt, + taskDueDate: taskDueDate.toISOString(), + today: today.toISOString(), + isOverdue, + }); + + return isOverdue; // Only overdue tasks }) .sort((a: TwentyTask, b: TwentyTask) => { // Sort by dueAt (oldest first) @@ -207,6 +259,16 @@ async function fetchTwentyTasks(): Promise { return dateA - dateB; }); + logger.debug('[TWENTY_CRM_TASKS] Tasks after filtering', { + count: tasks.length, + tasks: tasks.map(t => ({ + id: t.id, + title: t.title, + status: t.status, + dueAt: t.dueAt, + })), + }); + logger.debug('[TWENTY_CRM_TASKS] Successfully fetched tasks from Twenty CRM', { count: tasks.length, });