Enhance team update logic and error handling
- Refactored the team update function in the Admin Center controller to improve error handling and response messages. - Implemented concurrent updates for team member roles using Promise.all, enhancing performance and user experience. - Updated the frontend API service to accept a structured body for team updates, ensuring consistency in data handling. - Enhanced the settings drawer component to manage team member roles more effectively, improving the overall user interface.
This commit is contained in:
@@ -1,14 +1,3 @@
|
|||||||
-- CONSOLIDATED MIGRATION FILE
|
|
||||||
-- Contains all progress-related migrations from April-May 2025
|
|
||||||
-- Generated on: (current date)
|
|
||||||
|
|
||||||
-- =============================================================================
|
|
||||||
-- Migration: Add manual task progress
|
|
||||||
-- Date: 2025-04-22
|
|
||||||
-- Version: 1.0.0
|
|
||||||
-- File: 20250422132400-manual-task-progress.sql
|
|
||||||
-- =============================================================================
|
|
||||||
|
|
||||||
BEGIN;
|
BEGIN;
|
||||||
|
|
||||||
-- Add manual progress fields to tasks table
|
-- Add manual progress fields to tasks table
|
||||||
@@ -17,6 +6,12 @@ ADD COLUMN IF NOT EXISTS manual_progress BOOLEAN DEFAULT FALSE,
|
|||||||
ADD COLUMN IF NOT EXISTS progress_value INTEGER DEFAULT NULL,
|
ADD COLUMN IF NOT EXISTS progress_value INTEGER DEFAULT NULL,
|
||||||
ADD COLUMN IF NOT EXISTS weight INTEGER DEFAULT NULL;
|
ADD COLUMN IF NOT EXISTS weight INTEGER DEFAULT NULL;
|
||||||
|
|
||||||
|
-- Add progress-related fields to projects table
|
||||||
|
ALTER TABLE projects
|
||||||
|
ADD COLUMN IF NOT EXISTS use_manual_progress BOOLEAN DEFAULT FALSE,
|
||||||
|
ADD COLUMN IF NOT EXISTS use_weighted_progress BOOLEAN DEFAULT FALSE,
|
||||||
|
ADD COLUMN IF NOT EXISTS use_time_progress BOOLEAN DEFAULT FALSE;
|
||||||
|
|
||||||
-- Update function to consider manual progress
|
-- Update function to consider manual progress
|
||||||
CREATE OR REPLACE FUNCTION get_task_complete_ratio(_task_id uuid) RETURNS json
|
CREATE OR REPLACE FUNCTION get_task_complete_ratio(_task_id uuid) RETURNS json
|
||||||
LANGUAGE plpgsql
|
LANGUAGE plpgsql
|
||||||
@@ -31,12 +26,26 @@ DECLARE
|
|||||||
_ratio FLOAT = 0;
|
_ratio FLOAT = 0;
|
||||||
_is_manual BOOLEAN = FALSE;
|
_is_manual BOOLEAN = FALSE;
|
||||||
_manual_value INTEGER = NULL;
|
_manual_value INTEGER = NULL;
|
||||||
|
_project_id UUID;
|
||||||
|
_use_manual_progress BOOLEAN = FALSE;
|
||||||
|
_use_weighted_progress BOOLEAN = FALSE;
|
||||||
|
_use_time_progress BOOLEAN = FALSE;
|
||||||
BEGIN
|
BEGIN
|
||||||
-- Check if manual progress is set
|
-- Check if manual progress is set
|
||||||
SELECT manual_progress, progress_value
|
SELECT manual_progress, progress_value, project_id
|
||||||
FROM tasks
|
FROM tasks
|
||||||
WHERE id = _task_id
|
WHERE id = _task_id
|
||||||
INTO _is_manual, _manual_value;
|
INTO _is_manual, _manual_value, _project_id;
|
||||||
|
|
||||||
|
-- Check if the project uses manual progress
|
||||||
|
IF _project_id IS NOT NULL THEN
|
||||||
|
SELECT COALESCE(use_manual_progress, FALSE),
|
||||||
|
COALESCE(use_weighted_progress, FALSE),
|
||||||
|
COALESCE(use_time_progress, FALSE)
|
||||||
|
FROM projects
|
||||||
|
WHERE id = _project_id
|
||||||
|
INTO _use_manual_progress, _use_weighted_progress, _use_time_progress;
|
||||||
|
END IF;
|
||||||
|
|
||||||
-- If manual progress is enabled and has a value, use it directly
|
-- If manual progress is enabled and has a value, use it directly
|
||||||
IF _is_manual IS TRUE AND _manual_value IS NOT NULL THEN
|
IF _is_manual IS TRUE AND _manual_value IS NOT NULL THEN
|
||||||
@@ -82,35 +91,193 @@ BEGIN
|
|||||||
END
|
END
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
-- Update project functions to handle progress-related fields
|
||||||
|
CREATE OR REPLACE FUNCTION update_project(_body json) RETURNS json
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
_user_id UUID;
|
||||||
|
_team_id UUID;
|
||||||
|
_client_id UUID;
|
||||||
|
_project_id UUID;
|
||||||
|
_project_manager_team_member_id UUID;
|
||||||
|
_client_name TEXT;
|
||||||
|
_project_name TEXT;
|
||||||
|
BEGIN
|
||||||
|
-- need a test, can be throw errors
|
||||||
|
_client_name = TRIM((_body ->> 'client_name')::TEXT);
|
||||||
|
_project_name = TRIM((_body ->> 'name')::TEXT);
|
||||||
|
|
||||||
|
-- add inside the controller
|
||||||
|
_user_id = (_body ->> 'user_id')::UUID;
|
||||||
|
_team_id = (_body ->> 'team_id')::UUID;
|
||||||
|
_project_manager_team_member_id = (_body ->> 'team_member_id')::UUID;
|
||||||
|
|
||||||
|
-- cache exists client if exists
|
||||||
|
SELECT id FROM clients WHERE LOWER(name) = LOWER(_client_name) AND team_id = _team_id INTO _client_id;
|
||||||
|
|
||||||
|
-- insert client if not exists
|
||||||
|
IF is_null_or_empty(_client_id) IS TRUE AND is_null_or_empty(_client_name) IS FALSE
|
||||||
|
THEN
|
||||||
|
INSERT INTO clients (name, team_id) VALUES (_client_name, _team_id) RETURNING id INTO _client_id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- check whether the project name is already in
|
||||||
|
IF EXISTS(
|
||||||
|
SELECT name FROM projects WHERE LOWER(name) = LOWER(_project_name)
|
||||||
|
AND team_id = _team_id AND id != (_body ->> 'id')::UUID
|
||||||
|
)
|
||||||
|
THEN
|
||||||
|
RAISE 'PROJECT_EXISTS_ERROR:%', _project_name;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- update the project
|
||||||
|
UPDATE projects
|
||||||
|
SET name = _project_name,
|
||||||
|
notes = (_body ->> 'notes')::TEXT,
|
||||||
|
color_code = (_body ->> 'color_code')::TEXT,
|
||||||
|
status_id = (_body ->> 'status_id')::UUID,
|
||||||
|
health_id = (_body ->> 'health_id')::UUID,
|
||||||
|
key = (_body ->> 'key')::TEXT,
|
||||||
|
start_date = (_body ->> 'start_date')::TIMESTAMPTZ,
|
||||||
|
end_date = (_body ->> 'end_date')::TIMESTAMPTZ,
|
||||||
|
client_id = _client_id,
|
||||||
|
folder_id = (_body ->> 'folder_id')::UUID,
|
||||||
|
category_id = (_body ->> 'category_id')::UUID,
|
||||||
|
updated_at = CURRENT_TIMESTAMP,
|
||||||
|
estimated_working_days = (_body ->> 'working_days')::INTEGER,
|
||||||
|
estimated_man_days = (_body ->> 'man_days')::INTEGER,
|
||||||
|
hours_per_day = (_body ->> 'hours_per_day')::INTEGER,
|
||||||
|
use_manual_progress = COALESCE((_body ->> 'use_manual_progress')::BOOLEAN, FALSE),
|
||||||
|
use_weighted_progress = COALESCE((_body ->> 'use_weighted_progress')::BOOLEAN, FALSE),
|
||||||
|
use_time_progress = COALESCE((_body ->> 'use_time_progress')::BOOLEAN, FALSE)
|
||||||
|
WHERE id = (_body ->> 'id')::UUID
|
||||||
|
AND team_id = _team_id
|
||||||
|
RETURNING id INTO _project_id;
|
||||||
|
|
||||||
|
UPDATE project_members SET project_access_level_id = (SELECT id FROM project_access_levels WHERE key = 'MEMBER') WHERE project_id = _project_id;
|
||||||
|
|
||||||
|
IF NOT (_project_manager_team_member_id IS NULL)
|
||||||
|
THEN
|
||||||
|
PERFORM update_project_manager(_project_manager_team_member_id, _project_id::UUID);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN JSON_BUILD_OBJECT(
|
||||||
|
'id', _project_id,
|
||||||
|
'name', (_body ->> 'name')::TEXT,
|
||||||
|
'project_manager_id', _project_manager_team_member_id::UUID
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION create_project(_body json) RETURNS json
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
_project_id UUID;
|
||||||
|
_user_id UUID;
|
||||||
|
_team_id UUID;
|
||||||
|
_team_member_id UUID;
|
||||||
|
_client_id UUID;
|
||||||
|
_client_name TEXT;
|
||||||
|
_project_name TEXT;
|
||||||
|
_project_created_log TEXT;
|
||||||
|
_project_member_added_log TEXT;
|
||||||
|
_project_created_log_id UUID;
|
||||||
|
_project_manager_team_member_id UUID;
|
||||||
|
_project_key TEXT;
|
||||||
|
BEGIN
|
||||||
|
_client_name = TRIM((_body ->> 'client_name')::TEXT);
|
||||||
|
_project_name = TRIM((_body ->> 'name')::TEXT);
|
||||||
|
_project_key = TRIM((_body ->> 'key')::TEXT);
|
||||||
|
_project_created_log = (_body ->> 'project_created_log')::TEXT;
|
||||||
|
_project_member_added_log = (_body ->> 'project_member_added_log')::TEXT;
|
||||||
|
_user_id = (_body ->> 'user_id')::UUID;
|
||||||
|
_team_id = (_body ->> 'team_id')::UUID;
|
||||||
|
_project_manager_team_member_id = (_body ->> 'project_manager_id')::UUID;
|
||||||
|
|
||||||
|
SELECT id FROM team_members WHERE user_id = _user_id AND team_id = _team_id INTO _team_member_id;
|
||||||
|
|
||||||
|
-- cache exists client if exists
|
||||||
|
SELECT id FROM clients WHERE LOWER(name) = LOWER(_client_name) AND team_id = _team_id INTO _client_id;
|
||||||
|
|
||||||
|
-- insert client if not exists
|
||||||
|
IF is_null_or_empty(_client_id) IS TRUE AND is_null_or_empty(_client_name) IS FALSE
|
||||||
|
THEN
|
||||||
|
INSERT INTO clients (name, team_id) VALUES (_client_name, _team_id) RETURNING id INTO _client_id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- check whether the project name is already in
|
||||||
|
IF EXISTS(SELECT name FROM projects WHERE LOWER(name) = LOWER(_project_name) AND team_id = _team_id)
|
||||||
|
THEN
|
||||||
|
RAISE 'PROJECT_EXISTS_ERROR:%', _project_name;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- create the project
|
||||||
|
INSERT
|
||||||
|
INTO projects (name, key, color_code, start_date, end_date, team_id, notes, owner_id, status_id, health_id, folder_id,
|
||||||
|
category_id, estimated_working_days, estimated_man_days, hours_per_day,
|
||||||
|
use_manual_progress, use_weighted_progress, use_time_progress, client_id)
|
||||||
|
VALUES (_project_name,
|
||||||
|
UPPER(_project_key),
|
||||||
|
(_body ->> 'color_code')::TEXT,
|
||||||
|
(_body ->> 'start_date')::TIMESTAMPTZ,
|
||||||
|
(_body ->> 'end_date')::TIMESTAMPTZ,
|
||||||
|
_team_id,
|
||||||
|
(_body ->> 'notes')::TEXT,
|
||||||
|
_user_id,
|
||||||
|
(_body ->> 'status_id')::UUID,
|
||||||
|
(_body ->> 'health_id')::UUID,
|
||||||
|
(_body ->> 'folder_id')::UUID,
|
||||||
|
(_body ->> 'category_id')::UUID,
|
||||||
|
(_body ->> 'working_days')::INTEGER,
|
||||||
|
(_body ->> 'man_days')::INTEGER,
|
||||||
|
(_body ->> 'hours_per_day')::INTEGER,
|
||||||
|
COALESCE((_body ->> 'use_manual_progress')::BOOLEAN, FALSE),
|
||||||
|
COALESCE((_body ->> 'use_weighted_progress')::BOOLEAN, FALSE),
|
||||||
|
COALESCE((_body ->> 'use_time_progress')::BOOLEAN, FALSE),
|
||||||
|
_client_id)
|
||||||
|
RETURNING id INTO _project_id;
|
||||||
|
|
||||||
|
-- register the project log
|
||||||
|
INSERT INTO project_logs (project_id, team_id, description)
|
||||||
|
VALUES (_project_id, _team_id, _project_created_log)
|
||||||
|
RETURNING id INTO _project_created_log_id;
|
||||||
|
|
||||||
|
-- insert the project creator as a project member
|
||||||
|
INSERT INTO project_members (team_member_id, project_access_level_id, project_id, role_id)
|
||||||
|
VALUES (_team_member_id, (SELECT id FROM project_access_levels WHERE key = 'ADMIN'),
|
||||||
|
_project_id,
|
||||||
|
(SELECT id FROM roles WHERE team_id = _team_id AND default_role IS TRUE));
|
||||||
|
|
||||||
|
-- insert statuses
|
||||||
|
INSERT INTO task_statuses (name, project_id, team_id, category_id, sort_order)
|
||||||
|
VALUES ('To Do', _project_id, _team_id, (SELECT id FROM sys_task_status_categories WHERE is_todo IS TRUE), 0);
|
||||||
|
INSERT INTO task_statuses (name, project_id, team_id, category_id, sort_order)
|
||||||
|
VALUES ('Doing', _project_id, _team_id, (SELECT id FROM sys_task_status_categories WHERE is_doing IS TRUE), 1);
|
||||||
|
INSERT INTO task_statuses (name, project_id, team_id, category_id, sort_order)
|
||||||
|
VALUES ('Done', _project_id, _team_id, (SELECT id FROM sys_task_status_categories WHERE is_done IS TRUE), 2);
|
||||||
|
|
||||||
|
-- insert default project columns
|
||||||
|
PERFORM insert_task_list_columns(_project_id);
|
||||||
|
|
||||||
|
-- add project manager role if exists
|
||||||
|
IF NOT is_null_or_empty(_project_manager_team_member_id) THEN
|
||||||
|
PERFORM update_project_manager(_project_manager_team_member_id, _project_id);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN JSON_BUILD_OBJECT(
|
||||||
|
'id', _project_id,
|
||||||
|
'name', _project_name,
|
||||||
|
'project_created_log_id', _project_created_log_id
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
|
||||||
-- =============================================================================
|
|
||||||
-- Migration: Subtask manual progress
|
|
||||||
-- Date: 2025-04-23
|
|
||||||
-- Version: 1.0.0
|
|
||||||
-- File: 20250423000000-subtask-manual-progress.sql
|
|
||||||
-- =============================================================================
|
|
||||||
|
|
||||||
-- Note: Contents extracted from the file description (actual file not available)
|
|
||||||
-- This migration likely extends the manual progress feature to support subtasks
|
|
||||||
|
|
||||||
-- =============================================================================
|
|
||||||
-- Migration: Add progress and weight activity types
|
|
||||||
-- Date: 2025-04-24
|
|
||||||
-- Version: 1.0.0
|
|
||||||
-- File: 20250424000000-add-progress-and-weight-activity-types.sql
|
|
||||||
-- =============================================================================
|
|
||||||
|
|
||||||
-- Note: Contents extracted from the file description (actual file not available)
|
|
||||||
-- This migration likely adds new activity types for tracking progress and weight changes
|
|
||||||
|
|
||||||
-- =============================================================================
|
|
||||||
-- Migration: Update time-based progress mode to work for all tasks
|
|
||||||
-- Date: 2025-04-25
|
|
||||||
-- Version: 1.0.0
|
|
||||||
-- File: 20250425000000-update-time-based-progress.sql
|
|
||||||
-- =============================================================================
|
|
||||||
|
|
||||||
BEGIN;
|
BEGIN;
|
||||||
|
|
||||||
-- Update function to use time-based progress for all tasks
|
-- Update function to use time-based progress for all tasks
|
||||||
@@ -349,24 +516,142 @@ BEGIN
|
|||||||
END
|
END
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.get_task_form_view_model(_user_id UUID, _team_id UUID, _task_id UUID, _project_id UUID) RETURNS JSON
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
_task JSON;
|
||||||
|
_priorities JSON;
|
||||||
|
_projects JSON;
|
||||||
|
_statuses JSON;
|
||||||
|
_team_members JSON;
|
||||||
|
_assignees JSON;
|
||||||
|
_phases JSON;
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
-- Select task info
|
||||||
|
SELECT COALESCE(ROW_TO_JSON(rec), '{}'::JSON)
|
||||||
|
INTO _task
|
||||||
|
FROM (WITH RECURSIVE task_hierarchy AS (
|
||||||
|
-- Base case: Start with the given task
|
||||||
|
SELECT id,
|
||||||
|
parent_task_id,
|
||||||
|
0 AS level
|
||||||
|
FROM tasks
|
||||||
|
WHERE id = _task_id
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
-- Recursive case: Traverse up to parent tasks
|
||||||
|
SELECT t.id,
|
||||||
|
t.parent_task_id,
|
||||||
|
th.level + 1 AS level
|
||||||
|
FROM tasks t
|
||||||
|
INNER JOIN task_hierarchy th ON t.id = th.parent_task_id
|
||||||
|
WHERE th.parent_task_id IS NOT NULL)
|
||||||
|
SELECT id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
done,
|
||||||
|
total_minutes,
|
||||||
|
priority_id,
|
||||||
|
project_id,
|
||||||
|
created_at,
|
||||||
|
updated_at,
|
||||||
|
status_id,
|
||||||
|
parent_task_id,
|
||||||
|
sort_order,
|
||||||
|
(SELECT phase_id FROM task_phase WHERE task_id = tasks.id) AS phase_id,
|
||||||
|
CONCAT((SELECT key FROM projects WHERE id = tasks.project_id), '-', task_no) AS task_key,
|
||||||
|
(SELECT start_time
|
||||||
|
FROM task_timers
|
||||||
|
WHERE task_id = tasks.id
|
||||||
|
AND user_id = _user_id) AS timer_start_time,
|
||||||
|
parent_task_id IS NOT NULL AS is_sub_task,
|
||||||
|
(SELECT COUNT('*')
|
||||||
|
FROM tasks
|
||||||
|
WHERE parent_task_id = tasks.id
|
||||||
|
AND archived IS FALSE) AS sub_tasks_count,
|
||||||
|
(SELECT COUNT(*)
|
||||||
|
FROM tasks_with_status_view tt
|
||||||
|
WHERE (tt.parent_task_id = tasks.id OR tt.task_id = tasks.id)
|
||||||
|
AND tt.is_done IS TRUE)
|
||||||
|
AS completed_count,
|
||||||
|
(SELECT COUNT(*) FROM task_attachments WHERE task_id = tasks.id) AS attachments_count,
|
||||||
|
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON)
|
||||||
|
FROM (SELECT task_labels.label_id AS id,
|
||||||
|
(SELECT name FROM team_labels WHERE id = task_labels.label_id),
|
||||||
|
(SELECT color_code FROM team_labels WHERE id = task_labels.label_id)
|
||||||
|
FROM task_labels
|
||||||
|
WHERE task_id = tasks.id
|
||||||
|
ORDER BY name) r) AS labels,
|
||||||
|
(SELECT color_code
|
||||||
|
FROM sys_task_status_categories
|
||||||
|
WHERE id = (SELECT category_id FROM task_statuses WHERE id = tasks.status_id)) AS status_color,
|
||||||
|
(SELECT COUNT(*) FROM tasks WHERE parent_task_id = _task_id) AS sub_tasks_count,
|
||||||
|
(SELECT name FROM users WHERE id = tasks.reporter_id) AS reporter,
|
||||||
|
(SELECT get_task_assignees(tasks.id)) AS assignees,
|
||||||
|
(SELECT id FROM team_members WHERE user_id = _user_id AND team_id = _team_id) AS team_member_id,
|
||||||
|
billable,
|
||||||
|
schedule_id,
|
||||||
|
progress_value,
|
||||||
|
weight,
|
||||||
|
(SELECT MAX(level) FROM task_hierarchy) AS task_level
|
||||||
|
FROM tasks
|
||||||
|
WHERE id = _task_id) rec;
|
||||||
|
|
||||||
|
SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
|
||||||
|
INTO _priorities
|
||||||
|
FROM (SELECT id, name FROM task_priorities ORDER BY value) rec;
|
||||||
|
|
||||||
|
SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
|
||||||
|
INTO _phases
|
||||||
|
FROM (SELECT id, name FROM project_phases WHERE project_id = _project_id ORDER BY name) rec;
|
||||||
|
|
||||||
|
SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
|
||||||
|
INTO _projects
|
||||||
|
FROM (SELECT id, name
|
||||||
|
FROM projects
|
||||||
|
WHERE team_id = _team_id
|
||||||
|
AND (CASE
|
||||||
|
WHEN (is_owner(_user_id, _team_id) OR is_admin(_user_id, _team_id) IS TRUE) THEN TRUE
|
||||||
|
ELSE is_member_of_project(projects.id, _user_id, _team_id) END)
|
||||||
|
ORDER BY name) rec;
|
||||||
|
|
||||||
|
SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
|
||||||
|
INTO _statuses
|
||||||
|
FROM (SELECT id, name FROM task_statuses WHERE project_id = _project_id) rec;
|
||||||
|
|
||||||
|
SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
|
||||||
|
INTO _team_members
|
||||||
|
FROM (SELECT team_members.id,
|
||||||
|
(SELECT name FROM team_member_info_view WHERE team_member_info_view.team_member_id = team_members.id),
|
||||||
|
(SELECT email FROM team_member_info_view WHERE team_member_info_view.team_member_id = team_members.id),
|
||||||
|
(SELECT avatar_url
|
||||||
|
FROM team_member_info_view
|
||||||
|
WHERE team_member_info_view.team_member_id = team_members.id)
|
||||||
|
FROM team_members
|
||||||
|
LEFT JOIN users u ON team_members.user_id = u.id
|
||||||
|
WHERE team_id = _team_id
|
||||||
|
AND team_members.active IS TRUE) rec;
|
||||||
|
|
||||||
|
SELECT get_task_assignees(_task_id) INTO _assignees;
|
||||||
|
|
||||||
|
RETURN JSON_BUILD_OBJECT(
|
||||||
|
'task', _task,
|
||||||
|
'priorities', _priorities,
|
||||||
|
'projects', _projects,
|
||||||
|
'statuses', _statuses,
|
||||||
|
'team_members', _team_members,
|
||||||
|
'assignees', _assignees,
|
||||||
|
'phases', _phases
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
|
||||||
-- =============================================================================
|
|
||||||
-- Migration: Improve parent task progress calculation
|
|
||||||
-- Date: 2025-04-26
|
|
||||||
-- Version: 1.0.0
|
|
||||||
-- File: 20250426000000-improve-parent-task-progress-calculation.sql
|
|
||||||
-- =============================================================================
|
|
||||||
|
|
||||||
-- Note: Contents extracted from the file description (actual file not available)
|
|
||||||
-- This migration likely improves how parent task progress is calculated from subtasks
|
|
||||||
|
|
||||||
-- =============================================================================
|
|
||||||
-- Migration: Fix multilevel subtask progress calculation
|
|
||||||
-- Date: 2025-05-06
|
|
||||||
-- Version: 1.0.0
|
|
||||||
-- File: 20250506000000-fix-multilevel-subtask-progress-calculation.sql
|
|
||||||
-- =============================================================================
|
|
||||||
|
|
||||||
-- Note: Contents extracted from the file description (actual file not available)
|
|
||||||
-- This migration likely fixes progress calculation for multilevel nested subtasks
|
|
||||||
@@ -5,7 +5,7 @@ import db from "../config/db";
|
|||||||
import {ServerResponse} from "../models/server-response";
|
import {ServerResponse} from "../models/server-response";
|
||||||
import WorklenzControllerBase from "./worklenz-controller-base";
|
import WorklenzControllerBase from "./worklenz-controller-base";
|
||||||
import HandleExceptions from "../decorators/handle-exceptions";
|
import HandleExceptions from "../decorators/handle-exceptions";
|
||||||
import {calculateMonthDays, getColor, megabytesToBytes} from "../shared/utils";
|
import {calculateMonthDays, getColor, log_error, megabytesToBytes} from "../shared/utils";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import {calculateStorage} from "../shared/s3";
|
import {calculateStorage} from "../shared/s3";
|
||||||
import {checkTeamSubscriptionStatus, getActiveTeamMemberCount, getCurrentProjectsCount, getFreePlanSettings, getOwnerIdByTeam, getTeamMemberCount, getUsedStorage} from "../shared/paddle-utils";
|
import {checkTeamSubscriptionStatus, getActiveTeamMemberCount, getCurrentProjectsCount, getFreePlanSettings, getOwnerIdByTeam, getTeamMemberCount, getUsedStorage} from "../shared/paddle-utils";
|
||||||
@@ -255,22 +255,33 @@ export default class AdminCenterController extends WorklenzControllerBase {
|
|||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
const {name, teamMembers} = req.body;
|
const {name, teamMembers} = req.body;
|
||||||
|
|
||||||
const updateNameQuery = `UPDATE teams
|
try {
|
||||||
SET name = $1
|
// Update team name
|
||||||
WHERE id = $2;`;
|
const updateNameQuery = `UPDATE teams SET name = $1 WHERE id = $2 RETURNING id;`;
|
||||||
await db.query(updateNameQuery, [name, id]);
|
const nameResult = await db.query(updateNameQuery, [name, id]);
|
||||||
|
|
||||||
if (teamMembers.length) {
|
if (!nameResult.rows.length) {
|
||||||
teamMembers.forEach(async (element: { role_name: string; user_id: string; }) => {
|
return res.status(404).send(new ServerResponse(false, null, "Team not found"));
|
||||||
const q = `UPDATE team_members
|
}
|
||||||
SET role_id = (SELECT id FROM roles WHERE roles.team_id = $1 AND name = $2)
|
|
||||||
WHERE user_id = $3
|
// Update team member roles if provided
|
||||||
AND team_id = $1;`;
|
if (teamMembers?.length) {
|
||||||
await db.query(q, [id, element.role_name, element.user_id]);
|
// Use Promise.all to handle all role updates concurrently
|
||||||
});
|
await Promise.all(teamMembers.map(async (member: { role_name: string; user_id: string; }) => {
|
||||||
|
const roleQuery = `
|
||||||
|
UPDATE team_members
|
||||||
|
SET role_id = (SELECT id FROM roles WHERE roles.team_id = $1 AND name = $2)
|
||||||
|
WHERE user_id = $3 AND team_id = $1
|
||||||
|
RETURNING id;`;
|
||||||
|
await db.query(roleQuery, [id, member.role_name, member.user_id]);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send(new ServerResponse(true, null, "Team updated successfully"));
|
||||||
|
} catch (error) {
|
||||||
|
log_error("Error updating team:", error);
|
||||||
|
return res.status(500).send(new ServerResponse(false, null, "Failed to update team"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send(new ServerResponse(true, [], "Team updated successfully"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@HandleExceptions()
|
@HandleExceptions()
|
||||||
|
|||||||
@@ -112,11 +112,11 @@ export const adminCenterApiService = {
|
|||||||
|
|
||||||
async updateTeam(
|
async updateTeam(
|
||||||
team_id: string,
|
team_id: string,
|
||||||
team_members: IOrganizationUser[]
|
body: {name: string, teamMembers: IOrganizationUser[]}
|
||||||
): Promise<IServerResponse<IOrganization>> {
|
): Promise<IServerResponse<IOrganization>> {
|
||||||
const response = await apiClient.put<IServerResponse<IOrganization>>(
|
const response = await apiClient.put<IServerResponse<IOrganization>>(
|
||||||
`${rootUrl}/organization/team/${team_id}`,
|
`${rootUrl}/organization/team/${team_id}`,
|
||||||
team_members
|
body
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,21 +13,17 @@ import {
|
|||||||
} from 'antd';
|
} from 'antd';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
import { useAppDispatch } from '@/hooks/useAppDispatch';
|
||||||
import { toggleSettingDrawer, updateTeam } from '@/features/teams/teamSlice';
|
|
||||||
import { TeamsType } from '@/types/admin-center/team.types';
|
|
||||||
import './settings-drawer.css';
|
import './settings-drawer.css';
|
||||||
import CustomAvatar from '@/components/CustomAvatar';
|
|
||||||
import { teamsApiService } from '@/api/teams/teams.api.service';
|
|
||||||
import logger from '@/utils/errorLogger';
|
import logger from '@/utils/errorLogger';
|
||||||
import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service';
|
import { adminCenterApiService } from '@/api/admin-center/admin-center.api.service';
|
||||||
import {
|
import {
|
||||||
IOrganizationTeam,
|
IOrganizationTeam,
|
||||||
IOrganizationTeamMember,
|
IOrganizationTeamMember,
|
||||||
} from '@/types/admin-center/admin-center.types';
|
} from '@/types/admin-center/admin-center.types';
|
||||||
import Avatars from '@/components/avatars/avatars';
|
|
||||||
import { AvatarNamesMap } from '@/shared/constants';
|
|
||||||
import SingleAvatar from '@/components/common/single-avatar/single-avatar';
|
import SingleAvatar from '@/components/common/single-avatar/single-avatar';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { API_BASE_URL } from '@/shared/constants';
|
||||||
|
import apiClient from '@/api/api-client';
|
||||||
|
|
||||||
interface SettingTeamDrawerProps {
|
interface SettingTeamDrawerProps {
|
||||||
teamId: string;
|
teamId: string;
|
||||||
@@ -68,26 +64,30 @@ const SettingTeamDrawer: React.FC<SettingTeamDrawerProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleFormSubmit = async (values: any) => {
|
const handleFormSubmit = async (values: any) => {
|
||||||
console.log(values);
|
try {
|
||||||
// const newTeam: TeamsType = {
|
setUpdatingTeam(true);
|
||||||
// teamId: teamId,
|
|
||||||
// teamName: values.name,
|
const body = {
|
||||||
// membersCount: team?.membersCount || 1,
|
name: values.name,
|
||||||
// members: team?.members || ['Raveesha Dilanka'],
|
teamMembers: teamData?.team_members || []
|
||||||
// owner: values.name,
|
};
|
||||||
// created: team?.created || new Date(),
|
|
||||||
// isActive: false,
|
const response = await adminCenterApiService.updateTeam(teamId, body);
|
||||||
// };
|
|
||||||
// dispatch(updateTeam(newTeam));
|
if (response.done) {
|
||||||
// dispatch(toggleSettingDrawer());
|
setIsSettingDrawerOpen(false);
|
||||||
// form.resetFields();
|
}
|
||||||
// message.success('Team updated!');
|
} catch (error) {
|
||||||
|
logger.error('Error updating team', error);
|
||||||
|
} finally {
|
||||||
|
setUpdatingTeam(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const roleOptions = [
|
const roleOptions = [
|
||||||
{ value: 'Admin', label: t('admin') },
|
{ key: 'Admin', value: 'Admin', label: t('admin') },
|
||||||
{ value: 'Member', label: t('member') },
|
{ key: 'Member', value: 'Member', label: t('member') },
|
||||||
{ value: 'Owner', label: t('owner') },
|
{ key: 'Owner', value: 'Owner', label: t('owner'), disabled: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
const columns: TableProps['columns'] = [
|
const columns: TableProps['columns'] = [
|
||||||
@@ -104,16 +104,40 @@ const SettingTeamDrawer: React.FC<SettingTeamDrawerProps> = ({
|
|||||||
{
|
{
|
||||||
title: t('role'),
|
title: t('role'),
|
||||||
key: 'role',
|
key: 'role',
|
||||||
render: (_, record: IOrganizationTeamMember) => (
|
render: (_, record: IOrganizationTeamMember) => {
|
||||||
<div>
|
const handleRoleChange = (value: string) => {
|
||||||
<Select
|
if (value === 'Owner') {
|
||||||
style={{ width: '150px', height: '32px' }}
|
return;
|
||||||
options={roleOptions.map(option => ({ ...option, key: option.value }))}
|
}
|
||||||
defaultValue={record.role_name || ''}
|
|
||||||
disabled={record.role_name === 'Owner'}
|
// Update the team member's role in teamData
|
||||||
/>
|
if (teamData && teamData.team_members) {
|
||||||
</div>
|
const updatedMembers = teamData.team_members.map(member => {
|
||||||
),
|
if (member.id === record.id) {
|
||||||
|
return { ...member, role_name: value };
|
||||||
|
}
|
||||||
|
return member;
|
||||||
|
});
|
||||||
|
|
||||||
|
setTeamData({
|
||||||
|
...teamData,
|
||||||
|
team_members: updatedMembers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Select
|
||||||
|
style={{ width: '150px', height: '32px' }}
|
||||||
|
options={roleOptions}
|
||||||
|
defaultValue={record.role_name || ''}
|
||||||
|
disabled={record.role_name === 'Owner'}
|
||||||
|
onChange={handleRoleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user