diff --git a/backend/modules/tasks/routes.js b/backend/modules/tasks/routes.js index 1b1a87a..7957944 100644 --- a/backend/modules/tasks/routes.js +++ b/backend/modules/tasks/routes.js @@ -9,6 +9,7 @@ const { Task, TaskEvent, RecurringCompletion, + Project, sequelize, } = require('../../models'); const taskRepository = require('./repository'); @@ -32,7 +33,9 @@ const { filterTasksByParams } = require('./queries/query-builders'); const { getSafeTimezone, getTodayBoundsInUTC, + getUpcomingRangeInUTC, } = require('../../utils/timezone-utils'); +const permissionsService = require('../../services/permissionsService'); const { isValidUid } = require('../../utils/slug-utils'); const { @@ -212,6 +215,34 @@ router.get('/tasks', async (req, res) => { let tasks = await filterTasksByParams(req.query, userId, timezone); + // Fetch projects with upcoming due dates for the upcoming view + let upcomingProjects = []; + if (type === 'upcoming') { + const safeTimezone = getSafeTimezone(timezone); + const upcomingRange = getUpcomingRangeInUTC(safeTimezone, 7); + const { Op } = require('sequelize'); + + // Get projects owned by user or shared with user + const ownedOrShared = + await permissionsService.ownershipOrPermissionWhere( + 'project', + userId + ); + + upcomingProjects = await Project.findAll({ + where: { + ...ownedOrShared, + due_date_at: { + [Op.between]: [upcomingRange.start, upcomingRange.end], + }, + status: { + [Op.notIn]: ['completed', 'archived'], + }, + }, + order: [['due_date_at', 'ASC']], + }); + } + if (type === 'upcoming' && groupBy === 'day') { console.log('[DEBUG] Expanding recurring tasks for /upcoming'); console.log('[DEBUG] Total tasks before expansion:', tasks.length); @@ -299,6 +330,20 @@ router.get('/tasks', async (req, res) => { response.groupedTasks = serializedGrouped; } + // Add upcoming projects to response + if (type === 'upcoming' && upcomingProjects.length > 0) { + response.projects = upcomingProjects.map((project) => ({ + id: project.id, + uid: project.uid, + name: project.name, + status: project.status, + priority: project.priority, + due_date_at: project.due_date_at, + created_at: project.created_at, + updated_at: project.updated_at, + })); + } + await addDashboardLists( response, userId, diff --git a/frontend/components/Tasks.tsx b/frontend/components/Tasks.tsx index 484dd5f..7248e5d 100644 --- a/frontend/components/Tasks.tsx +++ b/frontend/components/Tasks.tsx @@ -44,6 +44,7 @@ const Tasks: React.FC = () => { const [tasks, setTasks] = useState([]); const projects = useStore((state: any) => state.projectsStore.projects); const [groupedTasks, setGroupedTasks] = useState(null); + const [upcomingProjects, setUpcomingProjects] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [dropdownOpen, setDropdownOpen] = useState(false); @@ -226,6 +227,7 @@ const Tasks: React.FC = () => { if (resetPagination) { setTasks(tasksData.tasks || []); setGroupedTasks(tasksData.groupedTasks || null); + setUpcomingProjects(tasksData.projects || []); if (!options?.disablePagination) { const limitToUse = options?.limitOverride ?? limit; setOffset(limitToUse); @@ -241,6 +243,9 @@ const Tasks: React.FC = () => { }; }); } + if (tasksData.projects) { + setUpcomingProjects((prev) => [...prev, ...(tasksData.projects || [])]); + } if (!options?.disablePagination) { const limitToUse = options?.limitOverride ?? limit; setOffset((prev) => prev + limitToUse); @@ -896,22 +901,54 @@ const Tasks: React.FC = () => { Object.keys(groupedTasks).length > 0) ? ( <> {query.get('type') === 'upcoming' ? ( - + <> + + {upcomingProjects.length > 0 && ( +
+

+ {t('projects.upcomingProjects', 'Upcoming Projects')} +

+
+ {upcomingProjects.map((project) => ( +
+
+ + {project.name} + +
+ {t('common.due', 'Due')}: + + {new Date(project.due_date_at).toLocaleDateString()} + +
+
+
+ ))} +
+
+ )} + ) : groupBy === 'project' ? (