Widget Devoir Finition

This commit is contained in:
alma 2026-01-24 15:32:47 +01:00
parent 485c49523e
commit b3ce739153
2 changed files with 73 additions and 31 deletions

View File

@ -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),

View File

@ -50,6 +50,7 @@ export function Duties() {
const [refreshing, setRefreshing] = useState(false);
const { triggerNotification } = useWidgetNotification();
const lastTaskCountRef = useRef<number>(-1);
const [leantimeDoneStatuses, setLeantimeDoneStatuses] = useState<Set<string>>(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<string>(['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;
}
}
const dueDate = getValidDate(task);
if (!dueDate) {
return false; // Exclude tasks without a due date
}
// FILTRE 2: Exclude tasks without a due date
const dueDate = getValidDate(task);
if (!dueDate) {
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';