Files
worklenz/worklenz-backend/src/controllers/home-page-controller.ts
chamiakJ 20e7d3c51a fix(home-page): exclude archived projects from task queries and update project color display
- Added a condition to exclude archived projects from the task retrieval queries in the HomePageController.
- Updated the TasksList component to display the project color instead of the phase color for better visual representation.
2025-07-12 06:46:23 +05:30

431 lines
16 KiB
TypeScript

import moment from "moment-timezone";
import db from "../config/db";
import HandleExceptions from "../decorators/handle-exceptions";
import {IWorkLenzRequest} from "../interfaces/worklenz-request";
import {IWorkLenzResponse} from "../interfaces/worklenz-response";
import {ServerResponse} from "../models/server-response";
import WorklenzControllerBase from "./worklenz-controller-base";
import momentTime from "moment-timezone";
interface ITask {
id: string,
name: string,
project_id: string,
parent_task_id: string | null,
is_sub_task: boolean,
parent_task_name: string | null,
status_id: string,
start_date: string | null,
end_date: string | null,
created_at: string | null,
team_id: string,
project_name: string,
project_color: string | null,
status: string,
status_color: string | null,
is_task: boolean,
done: boolean,
updated_at: string | null,
project_statuses: [{
id: string,
name: string | null,
color_code: string | null,
}]
}
export default class HomePageController extends WorklenzControllerBase {
private static readonly GROUP_BY_ASSIGNED_TO_ME = "0";
private static readonly GROUP_BY_ASSIGN_BY_ME = "1";
private static readonly ALL_TAB = "All";
private static readonly TODAY_TAB = "Today";
private static readonly UPCOMING_TAB = "Upcoming";
private static readonly OVERDUE_TAB = "Overdue";
private static readonly NO_DUE_DATE_TAB = "NoDueDate";
private static isValidGroup(groupBy: string) {
return groupBy === this.GROUP_BY_ASSIGNED_TO_ME
|| groupBy === this.GROUP_BY_ASSIGN_BY_ME;
}
private static isValidView(currentView: string) {
return currentView === this.ALL_TAB
|| currentView === this.TODAY_TAB
|| currentView === this.UPCOMING_TAB
|| currentView === this.OVERDUE_TAB
|| currentView === this.NO_DUE_DATE_TAB;
}
@HandleExceptions()
public static async createPersonalTask(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const q = `INSERT INTO personal_todo_list (name, color_code, user_id, index)
VALUES ($1, $2, $3, ((SELECT index FROM personal_todo_list ORDER BY index DESC LIMIT 1) + 1))
RETURNING id, name`;
const result = await db.query(q, [req.body.name, req.body.color_code, req.user?.id]);
const [data] = result.rows;
return res.status(200).send(new ServerResponse(true, data));
}
private static getTasksByGroupClosure(groupBy: string) {
switch (groupBy) {
case this.GROUP_BY_ASSIGN_BY_ME:
return `AND t.id IN (
SELECT task_id
FROM tasks_assignees
WHERE assigned_by = $2 AND team_id = $1)`;
case this.GROUP_BY_ASSIGNED_TO_ME:
default:
return `AND t.id IN (
SELECT task_id
FROM tasks_assignees
WHERE team_member_id = (SELECT id FROM team_members WHERE user_id = $2 AND team_id = $1))`;
}
}
private static getTasksByTabClosure(text: string) {
switch (text) {
case this.TODAY_TAB:
return `AND t.end_date::DATE = CURRENT_DATE::DATE`;
case this.UPCOMING_TAB:
return `AND t.end_date::DATE > CURRENT_DATE::DATE`;
case this.OVERDUE_TAB:
return `AND t.end_date::DATE < CURRENT_DATE::DATE`;
case this.NO_DUE_DATE_TAB:
return `AND t.end_date IS NULL`;
case this.ALL_TAB:
default:
return "";
}
}
private static async getTasksResult(groupByClosure: string, currentTabClosure: string, teamId: string, userId: string) {
const q = `
SELECT t.id,
t.name,
t.project_id,
t.parent_task_id,
t.parent_task_id IS NOT NULL AS is_sub_task,
(SELECT name FROM tasks WHERE id = t.parent_task_id) AS parent_task_name,
t.status_id,
t.start_date,
t.end_date,
t.created_at,
p.team_id,
p.name AS project_name,
p.color_code AS project_color,
(SELECT name FROM task_statuses WHERE id = t.status_id) AS status,
(SELECT color_code
FROM sys_task_status_categories
WHERE id = (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color,
TRUE AS is_task,
FALSE AS done,
t.updated_at,
(SELECT ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r)))
FROM (SELECT task_statuses.id AS id,
task_statuses.name AS name,
(SELECT color_code
FROM sys_task_status_categories
WHERE id = task_statuses.category_id)
FROM task_statuses
WHERE task_statuses.project_id = t.project_id) r) AS project_statuses
FROM tasks t
JOIN projects p ON t.project_id = p.id
WHERE t.archived IS FALSE
AND t.status_id NOT IN (SELECT id
FROM task_statuses
WHERE category_id NOT IN (SELECT id
FROM sys_task_status_categories
WHERE is_done IS FALSE))
AND NOT EXISTS(SELECT project_id
FROM archived_projects
WHERE project_id = p.id
AND user_id = $2)
${groupByClosure}
ORDER BY t.end_date ASC`;
const result = await db.query(q, [teamId, userId]);
return result.rows;
}
private static async getCountsResult(groupByClosure: string, teamId: string, userId: string) {
const q = `SELECT COUNT(*) AS total,
COUNT(CASE WHEN t.end_date::DATE = CURRENT_DATE::DATE THEN 1 END) AS today,
COUNT(CASE WHEN t.end_date::DATE > CURRENT_DATE::DATE THEN 1 END) AS upcoming,
COUNT(CASE WHEN t.end_date::DATE < CURRENT_DATE::DATE THEN 1 END) AS overdue,
COUNT(CASE WHEN t.end_date::DATE IS NULL THEN 1 END) AS no_due_date
FROM tasks t
JOIN projects p ON t.project_id = p.id
WHERE t.archived IS FALSE
AND t.status_id NOT IN (SELECT id
FROM task_statuses
WHERE category_id NOT IN (SELECT id
FROM sys_task_status_categories
WHERE is_done IS FALSE))
AND NOT EXISTS(SELECT project_id
FROM archived_projects
WHERE project_id = p.id
AND user_id = $3)
${groupByClosure}`;
const result = await db.query(q, [teamId, userId, userId]);
const [row] = result.rows;
return row;
}
@HandleExceptions()
public static async getTasks(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const teamId = req.user?.team_id;
const userId = req.user?.id;
const timeZone = req.query.time_zone as string;
const today = new Date();
const currentGroup = this.isValidGroup(req.query.group_by as string) ? req.query.group_by : this.GROUP_BY_ASSIGNED_TO_ME;
const currentTab = this.isValidView(req.query.current_tab as string) ? req.query.current_tab : this.ALL_TAB;
const groupByClosure = this.getTasksByGroupClosure(currentGroup as string);
let currentTabClosure = this.getTasksByTabClosure(currentTab as string);
const isCalendarView = req.query.is_calendar_view;
let result = await this.getTasksResult(groupByClosure, currentTabClosure, teamId as string, userId as string);
const counts = await this.getCountsByGroup(result, timeZone, today);
if (isCalendarView == "true") {
currentTabClosure = `AND t.end_date::DATE = '${req.query.selected_date}'`;
result = await this.groupBySingleDate(result, timeZone, req.query.selected_date as string);
} else {
result = await this.groupByDate(currentTab as string,result, timeZone, today);
}
// const counts = await this.getCountsResult(groupByClosure, teamId as string, userId as string);
const data = {
tasks: result,
total: counts.total,
today: counts.today,
upcoming: counts.upcoming,
overdue: counts.overdue,
no_due_date: counts.no_due_date,
};
return res.status(200).send(new ServerResponse(true, data));
}
private static async groupByDate(currentTab: string,tasks: any[], timeZone: string, today: Date) {
const formatToday = moment(today).format("YYYY-MM-DD");
const tasksReturn = [];
if (currentTab === this.ALL_TAB) {
for (const task of tasks) {
tasksReturn.push(task);
}
}
if (currentTab === this.NO_DUE_DATE_TAB) {
for (const task of tasks) {
if (!task.end_date) {
tasksReturn.push(task);
}
}
}
if (currentTab === this.TODAY_TAB) {
for (const task of tasks) {
if (task.end_date) {
const taskEndDate = momentTime.tz(task.end_date, `${timeZone}`).format("YYYY-MM-DD");
if (moment(taskEndDate).isSame(formatToday)) {
tasksReturn.push(task);
}
}
}
}
if (currentTab === this.UPCOMING_TAB) {
for (const task of tasks) {
if (task.end_date) {
const taskEndDate = momentTime.tz(task.end_date, `${timeZone}`).format("YYYY-MM-DD");
if (moment(taskEndDate).isAfter(formatToday)) {
tasksReturn.push(task);
}
}
}
}
if (currentTab === this.OVERDUE_TAB) {
for (const task of tasks) {
if (task.end_date) {
const taskEndDate = momentTime.tz(task.end_date, `${timeZone}`).format("YYYY-MM-DD");
if (moment(taskEndDate).isBefore(formatToday)) {
tasksReturn.push(task);
}
}
}
}
return tasksReturn;
}
private static async groupBySingleDate(tasks: any, timeZone: string, selectedDate: string) {
const formatSelectedDate = moment(selectedDate).format("YYYY-MM-DD");
const tasksReturn = [];
for (const task of tasks) {
if (task.end_date) {
const taskEndDate = momentTime.tz(task.end_date, `${timeZone}`).format("YYYY-MM-DD");
if (moment(taskEndDate).isSame(formatSelectedDate)) {
tasksReturn.push(task);
}
}
}
return tasksReturn;
}
private static async getCountsByGroup(tasks: any[], timeZone: string, today_: Date) {
let no_due_date = 0;
let today = 0;
let upcoming = 0;
let overdue = 0;
const total = tasks.length;
const formatToday = moment(today_).format("YYYY-MM-DD");
for (const task of tasks) {
if (!task.end_date) {
no_due_date = no_due_date + 1;
}
if (task.end_date) {
const taskEndDate = momentTime.tz(task.end_date, `${timeZone}`).format("YYYY-MM-DD");
if (moment(taskEndDate).isSame(formatToday)) {
today = today + 1;
}
if (moment(taskEndDate).isAfter(formatToday)) {
upcoming = upcoming + 1;
}
if (moment(taskEndDate).isBefore(formatToday)) {
overdue = overdue + 1;
}
}
}
return {
total,
today,
upcoming,
overdue,
no_due_date
};
}
@HandleExceptions()
public static async getPersonalTasks(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const user_id = req.user?.id;
const q = `SELECT ptl.id,
ptl.name,
ptl.created_at,
FALSE AS is_task,
ptl.done,
ptl.updated_at
FROM personal_todo_list ptl
WHERE ptl.user_id = $1
AND done IS FALSE
ORDER BY ptl.updated_at DESC`;
const results = await db.query(q, [user_id]);
return res.status(200).send(new ServerResponse(true, results.rows));
}
@HandleExceptions()
public static async getProjects(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const team_id = req.user?.team_id;
const user_id = req.user?.id;
const current_view = req.query.view;
const isFavorites = current_view === "1" ? ` AND EXISTS(SELECT user_id FROM favorite_projects WHERE user_id = $2 AND project_id = projects.id)` : "";
const isArchived = req.query.filter === "2"
? ` AND EXISTS(SELECT user_id FROM archived_projects WHERE user_id = $2 AND project_id = projects.id)`
: ` AND NOT EXISTS(SELECT user_id FROM archived_projects WHERE user_id = $2 AND project_id = projects.id)`;
const q = `SELECT id,
name,
EXISTS(SELECT user_id
FROM favorite_projects
WHERE user_id = $2
AND project_id = projects.id) AS favorite,
EXISTS(SELECT user_id
FROM archived_projects
WHERE user_id = $2
AND project_id = projects.id) AS archived,
color_code,
(SELECT COUNT(*)
FROM tasks
WHERE archived IS FALSE
AND project_id = projects.id) AS all_tasks_count,
(SELECT COUNT(*)
FROM tasks
WHERE archived IS FALSE
AND project_id = projects.id
AND status_id IN (SELECT id
FROM task_statuses
WHERE project_id = projects.id
AND category_id IN
(SELECT id FROM sys_task_status_categories WHERE is_done IS TRUE))) AS completed_tasks_count,
(SELECT COUNT(*)
FROM project_members
WHERE project_id = projects.id) AS members_count,
(SELECT get_project_members(projects.id)) AS names,
(SELECT CASE
WHEN ((SELECT MAX(updated_at)
FROM tasks
WHERE archived IS FALSE
AND project_id = projects.id) >
updated_at)
THEN (SELECT MAX(updated_at)
FROM tasks
WHERE archived IS FALSE
AND project_id = projects.id)
ELSE updated_at END) AS updated_at
FROM projects
WHERE team_id = $1 ${isArchived} ${isFavorites} AND is_member_of_project(projects.id , $2
, $1)
ORDER BY updated_at DESC`;
const result = await db.query(q, [team_id, user_id]);
return res.status(200).send(new ServerResponse(true, result.rows));
}
@HandleExceptions()
public static async getProjectsByTeam(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const team_id = req.user?.team_id;
const user_id = req.user?.id;
const q = `
SELECT id, name, color_code
FROM projects
WHERE team_id = $1
AND is_member_of_project(projects.id, $2, $1)
`;
const result = await db.query(q, [team_id, user_id]);
return res.status(200).send(new ServerResponse(true, result.rows));
}
@HandleExceptions()
public static async updatePersonalTask(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
const q = `
UPDATE personal_todo_list
SET done = TRUE
WHERE id = $1
RETURNING id
`;
await db.query(q, [req.body.id]);
return res.status(200).send(new ServerResponse(true, req.body.id));
}
}