widget leantime refactor

This commit is contained in:
alma 2026-01-15 22:35:30 +01:00
parent 4769fa7a97
commit ad047327c6

View File

@ -6,16 +6,14 @@ import { logger } from "@/lib/logger";
interface TwentyTask { interface TwentyTask {
id: string; id: string;
title: string; title: string;
body?: string; bodyV2?: any; // Can be rich text/JSON
dueAt?: string; dueAt?: string;
completedAt?: string; status?: string; // e.g., "Done", "Todo", etc.
type?: string; type?: string;
assigneeId?: string; assigneeId?: string;
assignee?: { assignee?: {
id: string; id: string;
firstName?: string; name?: string;
lastName?: string;
email?: string;
}; };
} }
@ -44,13 +42,16 @@ async function fetchTwentyTasks(): Promise<TwentyTask[]> {
const todayISO = today.toISOString(); const todayISO = today.toISOString();
// GraphQL query to fetch tasks from Twenty CRM // GraphQL query to fetch tasks from Twenty CRM
// Twenty CRM uses different query structures - trying tasks query first // Based on error messages, using correct field names:
// If this doesn't work, we may need to use REST API or check the actual schema // - 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
const query = ` const query = `
query GetOverdueTasks { query GetOverdueTasks {
tasks( tasks(
where: { filter: {
completedAt: { is: NULL } status: { neq: Done }
dueAt: { lt: "${todayISO}" } dueAt: { lt: "${todayISO}" }
} }
orderBy: { dueAt: AscNullsLast } orderBy: { dueAt: AscNullsLast }
@ -59,15 +60,13 @@ async function fetchTwentyTasks(): Promise<TwentyTask[]> {
node { node {
id id
title title
body bodyV2
dueAt dueAt
completedAt status
assigneeId assigneeId
assignee { assignee {
id id
firstName name
lastName
email
} }
} }
} }
@ -151,20 +150,32 @@ async function fetchTwentyTasks(): Promise<TwentyTask[]> {
// Transform Twenty CRM tasks to match our Task interface // Transform Twenty CRM tasks to match our Task interface
const tasks: TwentyTask[] = activitiesData.map((edge: any) => { const tasks: TwentyTask[] = activitiesData.map((edge: any) => {
const node = edge.node || edge; // Handle both edge.node and direct node 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 { return {
id: node.id, id: node.id,
title: node.title || 'Untitled Task', title: node.title || 'Untitled Task',
body: node.body || null, bodyV2: node.bodyV2 || null,
dueAt: node.dueAt || null, dueAt: node.dueAt || null,
completedAt: node.completedAt || null, status: node.status || null,
type: node.type || 'Task', type: node.type || 'Task',
assigneeId: node.assigneeId || null, assigneeId: node.assigneeId || null,
assignee: node.assignee ? { assignee: node.assignee ? {
id: node.assignee.id, id: node.assignee.id,
firstName: node.assignee.firstName || null, name: node.assignee.name || null,
lastName: node.assignee.lastName || null,
email: node.assignee.email || null,
} : null, } : null,
// Store extracted body text for easier access
_bodyText: bodyText,
}; };
}); });
@ -203,14 +214,14 @@ export async function GET(request: NextRequest) {
const transformedTasks = tasks.map((task) => ({ const transformedTasks = tasks.map((task) => ({
id: `twenty-${task.id}`, // Prefix to avoid conflicts with Leantime IDs id: `twenty-${task.id}`, // Prefix to avoid conflicts with Leantime IDs
headline: task.title, headline: task.title,
description: task.body || null, description: (task as any)._bodyText || null, // Use extracted body text
dateToFinish: task.dueAt || null, dateToFinish: task.dueAt || null,
projectName: 'Twenty CRM', projectName: 'Twenty CRM',
projectId: 0, projectId: 0,
status: task.completedAt ? 5 : 1, // 5 = Done, 1 = New status: task.status === 'Done' ? 5 : 1, // 5 = Done, 1 = New (or other status)
editorId: task.assigneeId || null, editorId: task.assigneeId || null,
editorFirstname: task.assignee?.firstName || null, editorFirstname: task.assignee?.name?.split(' ')[0] || null, // Extract first name from full name
editorLastname: task.assignee?.lastName || null, editorLastname: task.assignee?.name?.split(' ').slice(1).join(' ') || null, // Extract last name from full name
authorFirstname: null, authorFirstname: null,
authorLastname: null, authorLastname: null,
milestoneHeadline: null, milestoneHeadline: null,
@ -219,7 +230,7 @@ export async function GET(request: NextRequest) {
type: 'twenty-crm', type: 'twenty-crm',
dependingTicketId: null, dependingTicketId: null,
source: 'twenty-crm', // Add source identifier source: 'twenty-crm', // Add source identifier
url: process.env.TWENTY_CRM_URL ? `${process.env.TWENTY_CRM_URL}/object/activity/${task.id}` : null, url: process.env.TWENTY_CRM_URL ? `${process.env.TWENTY_CRM_URL}/object/task/${task.id}` : null,
})); }));
logger.debug('[TWENTY_CRM_TASKS] Transformed tasks', { logger.debug('[TWENTY_CRM_TASKS] Transformed tasks', {