diff --git a/app/api/leantime/tasks/route.ts b/app/api/leantime/tasks/route.ts index 036f3d0..88bddd3 100644 --- a/app/api/leantime/tasks/route.ts +++ b/app/api/leantime/tasks/route.ts @@ -246,7 +246,11 @@ export async function GET(request: NextRequest) { emailHash: Buffer.from(session.user.email.toLowerCase()).toString('base64').slice(0, 12), taskCount: filteredCachedTasks.length, }); - return NextResponse.json(filteredCachedTasks); + // Return tasks with done statuses for frontend filtering + return NextResponse.json({ + tasks: filteredCachedTasks, + doneStatuses: Array.from(doneStatusValues), + }); } } @@ -388,7 +392,11 @@ export async function GET(request: NextRequest) { // Cache the results await cacheTasksData(session.user.id, tasks); - return NextResponse.json(tasks); + // Return tasks with done statuses for frontend filtering + return NextResponse.json({ + tasks: tasks, + doneStatuses: Array.from(doneStatusValues), + }); } catch (error) { logger.error('[LEANTIME_TASKS] Error in tasks route', { error: error instanceof Error ? error.message : String(error), diff --git a/components/flow.tsx b/components/flow.tsx index 5941454..428f519 100644 --- a/components/flow.tsx +++ b/components/flow.tsx @@ -50,6 +50,7 @@ export function Duties() { const [refreshing, setRefreshing] = useState(false); const { triggerNotification } = useWidgetNotification(); const lastTaskCountRef = useRef(-1); + const [leantimeDoneStatuses, setLeantimeDoneStatuses] = useState>(new Set(['0', '3', '5'])); // Fallback values const getStatusLabel = (status: number): string => { switch (status) { @@ -114,9 +115,29 @@ export function Duties() { // Process Leantime tasks let leantimeTasks: Task[] = []; + let currentDoneStatuses = new Set(['0', '3', '5']); // Fallback values if (leantimeResponse.status === 'fulfilled' && leantimeResponse.value.ok) { - const leantimeData = await leantimeResponse.value.json(); - if (Array.isArray(leantimeData)) { + const leantimeResponseData = await leantimeResponse.value.json(); + + // Handle both formats: new format with {tasks, doneStatuses} or legacy array format + let leantimeData: Task[]; + if (Array.isArray(leantimeResponseData)) { + // Legacy format: direct array + leantimeData = leantimeResponseData; + } else if (leantimeResponseData.tasks && Array.isArray(leantimeResponseData.tasks)) { + // New format: object with tasks and doneStatuses + leantimeData = leantimeResponseData.tasks; + if (leantimeResponseData.doneStatuses && Array.isArray(leantimeResponseData.doneStatuses)) { + currentDoneStatuses = new Set(leantimeResponseData.doneStatuses); + setLeantimeDoneStatuses(currentDoneStatuses); + console.log('[Devoirs Widget] ✅ Using dynamic done statuses from API:', leantimeResponseData.doneStatuses); + } + } else { + console.warn('[Devoirs Widget] ⚠️ Invalid Leantime response format:', leantimeResponseData); + leantimeData = []; + } + + if (leantimeData.length > 0) { // Log ALL tasks with their statuses to see what we receive console.log('[Devoirs Widget] 📥 RAW Leantime tasks from API:', leantimeData.map((t: any) => ({ id: t.id, @@ -125,22 +146,6 @@ export function Duties() { statusType: typeof t.status, }))); leantimeTasks = leantimeData; - // Log tasks with status 0, 3, or 5 (done) to debug - const doneTasks = leantimeData.filter((t: Task) => { - const taskStatus = (t as any).status; // Use any to handle potential string/number mismatch - if (taskStatus === null || taskStatus === undefined) return false; - const statusNum = typeof taskStatus === 'string' ? parseInt(taskStatus, 10) : taskStatus; - const statusStr = typeof taskStatus === 'string' ? taskStatus.toLowerCase() : String(taskStatus).toLowerCase(); - return statusNum === 0 || statusNum === 3 || statusNum === 5 || statusStr === '0' || statusStr === '3' || statusStr === '5' || statusStr === 'done'; - }); - if (doneTasks.length > 0) { - console.warn('[Devoirs Widget] ⚠️ Received done tasks from Leantime API:', doneTasks.map((t: Task) => ({ - id: t.id, - headline: t.headline, - status: t.status, - statusType: typeof t.status, - }))); - } } } else { console.warn('Failed to fetch Leantime tasks:', leantimeResponse); @@ -165,7 +170,8 @@ export function Duties() { const rawStatus = (t as any).status; const statusNum = typeof rawStatus === 'string' ? parseInt(rawStatus, 10) : rawStatus; const statusStr = typeof rawStatus === 'string' ? rawStatus.toLowerCase().trim() : String(rawStatus).toLowerCase().trim(); - const isDone = statusNum === 0 || statusNum === 3 || statusNum === 5 || statusStr === '0' || statusStr === '3' || statusStr === '5' || statusStr === 'done'; + // Use dynamic done statuses instead of hardcoded values + const isDone = currentDoneStatuses.has(String(rawStatus)) || currentDoneStatuses.has(statusStr); return { id: t.id, headline: t.headline, @@ -240,30 +246,43 @@ export function Duties() { const todayDay = now.getDate(); const filteredTasks = allTasks.filter((task: Task) => { - // Exclude tasks with status Done - // In Leantime: status 3 = DONE (see api/leantime/status-labels/route.ts), also check status 5 + // FILTRE 1: Exclude tasks with status Done (using dynamic done statuses) const rawStatus = (task as any).status; // Use any to handle potential string/number mismatch - if (rawStatus === null || rawStatus === undefined) { - // If status is null/undefined, keep the task (let other filters handle it) - } else { - const taskStatus = typeof rawStatus === 'string' ? parseInt(rawStatus, 10) : rawStatus; - const statusStr = typeof rawStatus === 'string' ? rawStatus.toLowerCase() : String(rawStatus).toLowerCase(); - if (taskStatus === 0 || taskStatus === 3 || taskStatus === 5 || statusStr === '0' || statusStr === '3' || statusStr === '5' || statusStr === 'done') { + if (rawStatus !== null && rawStatus !== undefined) { + const statusStr = String(rawStatus); + // Check if this task's status is in the done statuses set (for Leantime tasks) + // For Twenty CRM tasks, we still check the old way since they don't use dynamic statuses + if (task.source === 'leantime' && currentDoneStatuses.has(statusStr)) { console.log('[Devoirs Widget] Filtering out done task:', { id: task.id, headline: task.headline, status: rawStatus, - taskStatus, + source: task.source, }); return false; } + // For Twenty CRM or legacy format, keep old check as fallback + if (task.source !== 'leantime') { + const taskStatus = typeof rawStatus === 'string' ? parseInt(rawStatus, 10) : rawStatus; + const statusStrLower = typeof rawStatus === 'string' ? rawStatus.toLowerCase() : String(rawStatus).toLowerCase(); + if (taskStatus === 0 || taskStatus === 3 || taskStatus === 5 || statusStrLower === '0' || statusStrLower === '3' || statusStrLower === '5' || statusStrLower === 'done') { + console.log('[Devoirs Widget] Filtering out done task (Twenty CRM):', { + id: task.id, + headline: task.headline, + status: rawStatus, + }); + return false; + } + } } + // FILTRE 2: Exclude tasks without a due date const dueDate = getValidDate(task); if (!dueDate) { - return false; // Exclude tasks without a due date + return false; } + // FILTRE 3: Keep tasks with due date <= today (overdue or due today, not future) // Use local date comparison to avoid timezone issues // Leantime dates with 'Z' are actually local time, not UTC - remove Z before parsing const dateStrForParsing = dueDate.endsWith('Z') ? dueDate.slice(0, -1) : dueDate; @@ -375,6 +394,21 @@ export function Duties() { if (rawStatus === null || rawStatus === undefined) { return true; // Keep tasks without status } + // Use dynamic done statuses for Leantime tasks + if (task.source === 'leantime') { + const statusStr = String(rawStatus); + if (currentDoneStatuses.has(statusStr)) { + console.warn('[Devoirs Widget] ⚠️ Filtering out done task before notification:', { + id: task.id, + headline: task.headline, + status: rawStatus, + source: task.source, + }); + return false; + } + return true; + } + // For Twenty CRM tasks, use old check as fallback const taskStatus = typeof rawStatus === 'string' ? parseInt(rawStatus, 10) : rawStatus; const statusStr = typeof rawStatus === 'string' ? rawStatus.toLowerCase().trim() : String(rawStatus).toLowerCase().trim(); const isDone = taskStatus === 0 || taskStatus === 3 || taskStatus === 5 || statusStr === '0' || statusStr === '3' || statusStr === '5' || statusStr === 'done';