diff --git a/worklenz-backend/src/controllers/home-page-controller.ts b/worklenz-backend/src/controllers/home-page-controller.ts index 34bfbe6..8d4f9b3 100644 --- a/worklenz-backend/src/controllers/home-page-controller.ts +++ b/worklenz-backend/src/controllers/home-page-controller.ts @@ -1,11 +1,12 @@ -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 { 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"; +import { TaskService } from "../services/tasks"; +import { ProjectService } from "../services/projects"; +import { GROUP_BY_ASSIGNED_TO_ME, ALL_TAB } from "../shared/constants"; +import { CreatePersonalTaskDto } from "../dtos"; interface ITask { id: string, @@ -27,174 +28,54 @@ interface ITask { done: boolean, updated_at: string | null, project_statuses: [{ - id: string, - name: string | null, - color_code: string | null, - }] + 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 { - 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]); + public static async createPersonalTask(request: IWorkLenzRequest, response: IWorkLenzResponse): Promise { + const payload : CreatePersonalTaskDto = { + name: request.body.name, + color_code: request.body.color, + user_id: request.user?.id + }; + const result = await TaskService.createPersonalTask(payload); 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 id 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)) - ${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)) - ${groupByClosure}`; - - const result = await db.query(q, [teamId, userId]); - const [row] = result.rows; - return row; + return response.status(200).send(new ServerResponse(true, data)); } @HandleExceptions() - public static async getTasks(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { - const teamId = req.user?.team_id; - const userId = req.user?.id; - const timeZone = req.query.time_zone as string; + public static async getTasks(request: IWorkLenzRequest, response: IWorkLenzResponse): Promise { + const teamId = request.user?.team_id; + const userId = request.user?.id; + const timeZone = request.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); - + const isCalendarView = request.query.is_calendar_view; + const selectedDate = request.query.selected_date; + + const currentGroup = TaskService.isValidGroup(request.query.group_by as string) ? request.query.group_by : GROUP_BY_ASSIGNED_TO_ME; + const currentTab = TaskService.isValidView(request.query.current_tab as string) ? request.query.current_tab : ALL_TAB; + const groupByClosure = TaskService.getTasksByGroupClosure(currentGroup); + let currentTabClosure = TaskService.getTasksByTabClosure(currentTab as string); + let result = await TaskService.getTasksResult(groupByClosure, currentTabClosure, teamId as string, userId as string); + let tasks: ITask[] = result.rows; + const counts = await TaskService.getTaskCountsByGroup(tasks, 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); + currentTabClosure = `AND t.end_date::DATE = '${selectedDate}'`; + tasks = await TaskService.getTaskGroupBySingleDate(tasks, timeZone, selectedDate as string); } else { - result = await this.groupByDate(currentTab as string,result, timeZone, today); + tasks = await TaskService.getTasksGroupByDate(currentTab as string, tasks, timeZone, today); } - // const counts = await this.getCountsResult(groupByClosure, teamId as string, userId as string); + // const counts = await TaskService.getCountsResult(groupByClosure, teamId as string, userId as string); const data = { - tasks: result, + tasks: tasks, total: counts.total, today: counts.today, upcoming: counts.upcoming, @@ -202,221 +83,37 @@ export default class HomePageController extends WorklenzControllerBase { 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 - }; - + return response.status(200).send(new ServerResponse(true, data)); } @HandleExceptions() - public static async getPersonalTasks(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { - 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)); + public static async getPersonalTasks(request: IWorkLenzRequest, response: IWorkLenzResponse): Promise { + const user_id = request.user?.id; + const results = await TaskService.getPersonalTasks(user_id); + return response.status(200).send(new ServerResponse(true, results.rows)); } @HandleExceptions() - public static async getProjects(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { - - 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)); + public static async getProjects(request: IWorkLenzRequest, response: IWorkLenzResponse): Promise { + const team_id = request.user?.team_id; + const user_id = request.user?.id; + const current_view = request.query.view; + const filter = request.query.filter; + const result = await ProjectService.getProjects(team_id, user_id, current_view, filter); + return response.status(200).send(new ServerResponse(true, result.rows)); } @HandleExceptions() - public static async getProjectsByTeam(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { - 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)); + public static async getProjectsByTeam(request: IWorkLenzRequest, response: IWorkLenzResponse): Promise { + const team_id = request.user?.team_id; + const user_id = request.user?.id; + const result = await ProjectService.getProjectsByTeam(team_id, user_id); + return response.status(200).send(new ServerResponse(true, result.rows)); } @HandleExceptions() - public static async updatePersonalTask(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise { - 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)); + public static async updatePersonalTask(request: IWorkLenzRequest, response: IWorkLenzResponse): Promise { + await TaskService.updatePersonalTask(request.body.id); + return response.status(200).send(new ServerResponse(true, request.body.id)); } } diff --git a/worklenz-backend/src/dtos/create-personal-task.dto.ts b/worklenz-backend/src/dtos/create-personal-task.dto.ts new file mode 100644 index 0000000..6c029e9 --- /dev/null +++ b/worklenz-backend/src/dtos/create-personal-task.dto.ts @@ -0,0 +1,5 @@ +export class CreatePersonalTaskDto { + name: string|undefined = ''; + color_code: string|undefined = ''; + user_id: string|undefined = ''; +} \ No newline at end of file diff --git a/worklenz-backend/src/dtos/index.ts b/worklenz-backend/src/dtos/index.ts new file mode 100644 index 0000000..145b87a --- /dev/null +++ b/worklenz-backend/src/dtos/index.ts @@ -0,0 +1 @@ +export * from './create-personal-task.dto'; \ No newline at end of file diff --git a/worklenz-backend/src/interfaces/worklenz-request.ts b/worklenz-backend/src/interfaces/worklenz-request.ts index d8722e7..00f792f 100644 --- a/worklenz-backend/src/interfaces/worklenz-request.ts +++ b/worklenz-backend/src/interfaces/worklenz-request.ts @@ -2,5 +2,7 @@ import {Request} from "express"; import {IPassportSession} from "./passport-session"; export interface IWorkLenzRequest extends Request { + body: any; + query: any; user?: IPassportSession; } diff --git a/worklenz-backend/src/services/projects/index.ts b/worklenz-backend/src/services/projects/index.ts new file mode 100644 index 0000000..0c087c3 --- /dev/null +++ b/worklenz-backend/src/services/projects/index.ts @@ -0,0 +1 @@ +export * from './project.service'; \ No newline at end of file diff --git a/worklenz-backend/src/services/projects/project.service.ts b/worklenz-backend/src/services/projects/project.service.ts new file mode 100644 index 0000000..574a3e4 --- /dev/null +++ b/worklenz-backend/src/services/projects/project.service.ts @@ -0,0 +1,69 @@ +import database from "../../config/db"; + +export class ProjectService { + + public static async getProjectsByTeam(team_id: string|undefined, user_id: string|undefined) { + const query = ` + SELECT id, name, color_code + FROM projects + WHERE team_id = $1 + AND is_member_of_project(projects.id, $2, $1) + `; + return await database.query(query, [team_id, user_id]); + } + + public static async getProjects(team_id: string|undefined, user_id: string|undefined, current_view: string, filter: string) { + + const isFavorites = current_view === "1" ? ` AND EXISTS(SELECT user_id FROM favorite_projects WHERE user_id = $2 AND project_id = projects.id)` : ""; + + const isArchived = 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 query = `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`; + + return await database.query(query, [team_id, user_id]); + } +} \ No newline at end of file diff --git a/worklenz-backend/src/services/tasks/index.ts b/worklenz-backend/src/services/tasks/index.ts new file mode 100644 index 0000000..0c70f5e --- /dev/null +++ b/worklenz-backend/src/services/tasks/index.ts @@ -0,0 +1 @@ +export * from './task.service'; \ No newline at end of file diff --git a/worklenz-backend/src/services/tasks/task.service.ts b/worklenz-backend/src/services/tasks/task.service.ts new file mode 100644 index 0000000..50b7751 --- /dev/null +++ b/worklenz-backend/src/services/tasks/task.service.ts @@ -0,0 +1,266 @@ +import moment from "moment-timezone"; +import momentTime from "moment-timezone"; +import database from "../../config/db"; +import { + TODAY_TAB, + UPCOMING_TAB, + OVERDUE_TAB, + NO_DUE_DATE_TAB, + ALL_TAB, + GROUP_BY_ASSIGN_BY_ME, + GROUP_BY_ASSIGNED_TO_ME +} from "../../shared/constants"; +import {CreatePersonalTaskDto} from "../../dtos"; + +export class TaskService { + + public static getTasksByGroupClosure(groupBy: string) { + switch (groupBy) { + case 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 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))`; + } + } + + public static getTasksByTabClosure(text: string) { + switch (text) { + case TODAY_TAB: + return `AND t.end_date::DATE = CURRENT_DATE::DATE`; + case UPCOMING_TAB: + return `AND t.end_date::DATE > CURRENT_DATE::DATE`; + case OVERDUE_TAB: + return `AND t.end_date::DATE < CURRENT_DATE::DATE`; + case NO_DUE_DATE_TAB: + return `AND t.end_date IS NULL`; + case ALL_TAB: + default: + return ""; + } + } + + public static async getTasksGroupByDate(currentTab: string, tasks: any[], timeZone: string, today: Date) { + const formatToday = moment(today).format("YYYY-MM-DD"); + + const tasksReturn = []; + + if (currentTab === ALL_TAB) { + for (const task of tasks) { + tasksReturn.push(task); + } + } + + if (currentTab === NO_DUE_DATE_TAB) { + for (const task of tasks) { + if (!task.end_date) { + tasksReturn.push(task); + } + } + } + + if (currentTab === 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 === 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 === 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; + } + + public static async createPersonalTask(data: CreatePersonalTaskDto) { + const query = `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`; + return await database.query(query, [data.name, data.color_code, data.user_id]); + } + + public static async getPersonalTasks(user_id: string|undefined) { + const query = `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`; + return await database.query(query, [user_id]); + } + + public static async getTaskCountsByGroup(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 + }; + } + + public static async getTaskGroupBySingleDate(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; + + } + + public static async getTaskCountsResult(groupByClosure: string, teamId: string, userId: string) { + const query = `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)) + ${groupByClosure}`; + + return await database.query(query, [teamId, userId]); + } + + public static async getTasksResult(groupByClosure: string, currentTabClosure: string, teamId: string, userId: string) { + const query = ` + 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 id 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)) + ${groupByClosure} + ORDER BY t.end_date ASC`; + + return await database.query(query, [teamId, userId]); + } + + public static async updatePersonalTask(id: string) { + const query = ` + UPDATE personal_todo_list + SET done = TRUE + WHERE id = $1 + RETURNING id + `; + return await database.query(query, [id]); + } + + public static isValidGroup(groupBy: string) { + return groupBy === GROUP_BY_ASSIGNED_TO_ME + || groupBy === GROUP_BY_ASSIGN_BY_ME; + } + + public static isValidView(currentView: string) { + return currentView === ALL_TAB + || currentView === TODAY_TAB + || currentView === UPCOMING_TAB + || currentView === OVERDUE_TAB + || currentView === NO_DUE_DATE_TAB; + } + +} \ No newline at end of file diff --git a/worklenz-backend/src/shared/constants.ts b/worklenz-backend/src/shared/constants.ts index 3a19717..3a816c9 100644 --- a/worklenz-backend/src/shared/constants.ts +++ b/worklenz-backend/src/shared/constants.ts @@ -131,3 +131,11 @@ export const DATE_RANGES = { LAST_QUARTER: "LAST_QUARTER", ALL_TIME: "ALL_TIME" }; + +export const GROUP_BY_ASSIGNED_TO_ME: string = "0"; +export const GROUP_BY_ASSIGN_BY_ME: string = "1"; +export const ALL_TAB: string = "All"; +export const TODAY_TAB: string = "Today"; +export const UPCOMING_TAB: string = "Upcoming"; +export const OVERDUE_TAB: string = "Overdue"; +export const NO_DUE_DATE_TAB: string = "NoDueDate";