Add task progress tracking methods documentation and enhance progress update logic
- Introduced a new markdown file detailing task progress tracking methods: manual, weighted, and time-based. - Updated backend logic to include complete ratio calculations for tasks. - Improved socket command for task progress updates, enabling recursive updates for ancestor tasks. - Enhanced frontend components to reflect progress changes based on the selected tracking method, including updates to task display and progress input handling. - Added support for manual progress flag in task model to facilitate accurate progress representation.
This commit is contained in:
244
task-progress-methods.md
Normal file
244
task-progress-methods.md
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
# Task Progress Tracking Methods in WorkLenz
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
WorkLenz supports three different methods for tracking task progress, each suitable for different project management approaches:
|
||||||
|
|
||||||
|
1. **Manual Progress** - Direct input of progress percentages
|
||||||
|
2. **Weighted Progress** - Tasks have weights that affect overall progress calculation
|
||||||
|
3. **Time-based Progress** - Progress calculated based on estimated time vs. time spent
|
||||||
|
|
||||||
|
These modes can be selected when creating or editing a project in the project drawer.
|
||||||
|
|
||||||
|
## 1. Manual Progress Mode
|
||||||
|
|
||||||
|
This mode allows direct input of progress percentages for individual tasks without subtasks.
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
- Enabled by setting `use_manual_progress` to true in the project settings
|
||||||
|
- Progress is updated through the `on-update-task-progress.ts` socket event handler
|
||||||
|
- The UI shows a manual progress input slider in the task drawer for tasks without subtasks
|
||||||
|
- Updates the database with `progress_value` and sets `manual_progress` flag to true
|
||||||
|
|
||||||
|
**Calculation Logic:**
|
||||||
|
- For tasks without subtasks: Uses the manually set progress value
|
||||||
|
- For parent tasks: Calculates the average of all subtask progress values
|
||||||
|
- Subtask progress comes from either manual values or completion status (0% or 100%)
|
||||||
|
|
||||||
|
**Code Example:**
|
||||||
|
```typescript
|
||||||
|
// Manual progress update via socket.io
|
||||||
|
socket?.emit(SocketEvents.UPDATE_TASK_PROGRESS.toString(), JSON.stringify({
|
||||||
|
task_id: task.id,
|
||||||
|
progress_value: value,
|
||||||
|
parent_task_id: task.parent_task_id
|
||||||
|
}));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Showing Progress in Subtask Rows
|
||||||
|
|
||||||
|
When manual progress is enabled in a project, progress is shown in the following ways:
|
||||||
|
|
||||||
|
1. **In Task List Views**:
|
||||||
|
- Subtasks display their individual progress values in the progress column
|
||||||
|
- Parent tasks display the calculated average progress of all subtasks
|
||||||
|
|
||||||
|
2. **Implementation Details**:
|
||||||
|
- The progress values are stored in the `progress_value` column in the database
|
||||||
|
- For subtasks with manual progress set, the value is shown directly
|
||||||
|
- For subtasks without manual progress, the completion status determines the value (0% or 100%)
|
||||||
|
- The task view model includes both `progress` and `complete_ratio` properties
|
||||||
|
|
||||||
|
**Relevant Components:**
|
||||||
|
```typescript
|
||||||
|
// From task-list-progress-cell.tsx
|
||||||
|
const TaskListProgressCell = ({ task }: TaskListProgressCellProps) => {
|
||||||
|
return task.is_sub_task ? null : (
|
||||||
|
<Tooltip title={`${task.completed_count || 0} / ${task.total_tasks_count || 0}`}>
|
||||||
|
<Progress
|
||||||
|
percent={task.complete_ratio || 0}
|
||||||
|
type="circle"
|
||||||
|
size={24}
|
||||||
|
style={{ cursor: 'default' }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Task Progress Calculation in Backend:**
|
||||||
|
```typescript
|
||||||
|
// From tasks-controller-base.ts
|
||||||
|
// For tasks without subtasks, respect manual progress if set
|
||||||
|
if (task.manual_progress === true && task.progress_value !== null) {
|
||||||
|
// For manually set progress, use that value directly
|
||||||
|
task.progress = parseInt(task.progress_value);
|
||||||
|
task.complete_ratio = parseInt(task.progress_value);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Weighted Progress Mode
|
||||||
|
|
||||||
|
This mode allows assigning different weights to subtasks to reflect their relative importance in the overall task or project progress.
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
- Enabled by setting `use_weighted_progress` to true in the project settings
|
||||||
|
- Weights are updated through the `on-update-task-weight.ts` socket event handler
|
||||||
|
- The UI shows a weight input for subtasks in the task drawer
|
||||||
|
- Default weight is 100 if not specified
|
||||||
|
|
||||||
|
**Calculation Logic:**
|
||||||
|
- Progress is calculated using a weighted average: `SUM(progress_value * weight) / SUM(weight)`
|
||||||
|
- This gives more influence to tasks with higher weights
|
||||||
|
- A parent task's progress is the weighted average of its subtasks' progress
|
||||||
|
|
||||||
|
**Code Example:**
|
||||||
|
```typescript
|
||||||
|
// Weight update via socket.io
|
||||||
|
socket?.emit(SocketEvents.UPDATE_TASK_WEIGHT.toString(), JSON.stringify({
|
||||||
|
task_id: task.id,
|
||||||
|
weight: value,
|
||||||
|
parent_task_id: task.parent_task_id
|
||||||
|
}));
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Time-based Progress Mode
|
||||||
|
|
||||||
|
This mode calculates progress based on estimated time vs. actual time spent.
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
- Enabled by setting `use_time_progress` to true in the project settings
|
||||||
|
- Uses task time estimates (hours and minutes) for calculation
|
||||||
|
- No separate socket handler needed as it's calculated automatically
|
||||||
|
|
||||||
|
**Calculation Logic:**
|
||||||
|
- Progress is calculated using time as the weight: `SUM(progress_value * estimated_minutes) / SUM(estimated_minutes)`
|
||||||
|
- For tasks with time tracking, estimated vs. actual time can be factored in
|
||||||
|
- Parent task progress is weighted by the estimated time of each subtask
|
||||||
|
|
||||||
|
**SQL Example:**
|
||||||
|
```sql
|
||||||
|
WITH subtask_progress AS (
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN manual_progress IS TRUE AND progress_value IS NOT NULL THEN
|
||||||
|
progress_value
|
||||||
|
ELSE
|
||||||
|
CASE
|
||||||
|
WHEN EXISTS(
|
||||||
|
SELECT 1
|
||||||
|
FROM tasks_with_status_view
|
||||||
|
WHERE tasks_with_status_view.task_id = t.id
|
||||||
|
AND is_done IS TRUE
|
||||||
|
) THEN 100
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
END AS progress_value,
|
||||||
|
COALESCE(total_hours * 60 + total_minutes, 0) AS estimated_minutes
|
||||||
|
FROM tasks t
|
||||||
|
WHERE t.parent_task_id = _task_id
|
||||||
|
AND t.archived IS FALSE
|
||||||
|
)
|
||||||
|
SELECT COALESCE(
|
||||||
|
SUM(progress_value * estimated_minutes) / NULLIF(SUM(estimated_minutes), 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
FROM subtask_progress
|
||||||
|
INTO _ratio;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Default Progress Tracking (when no special mode is selected)
|
||||||
|
|
||||||
|
If no specific progress mode is enabled, the system falls back to a traditional completion-based calculation:
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
- Default mode when all three special modes are disabled
|
||||||
|
- Based on task completion status only
|
||||||
|
|
||||||
|
**Calculation Logic:**
|
||||||
|
- For tasks without subtasks: 0% if not done, 100% if done
|
||||||
|
- For parent tasks: `(completed_tasks / total_tasks) * 100`
|
||||||
|
- Counts both the parent and all subtasks in the calculation
|
||||||
|
|
||||||
|
**SQL Example:**
|
||||||
|
```sql
|
||||||
|
-- Traditional calculation based on completion status
|
||||||
|
SELECT (CASE
|
||||||
|
WHEN EXISTS(SELECT 1
|
||||||
|
FROM tasks_with_status_view
|
||||||
|
WHERE tasks_with_status_view.task_id = _task_id
|
||||||
|
AND is_done IS TRUE) THEN 1
|
||||||
|
ELSE 0 END)
|
||||||
|
INTO _parent_task_done;
|
||||||
|
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM tasks_with_status_view
|
||||||
|
WHERE parent_task_id = _task_id
|
||||||
|
AND is_done IS TRUE
|
||||||
|
INTO _sub_tasks_done;
|
||||||
|
|
||||||
|
_total_completed = _parent_task_done + _sub_tasks_done;
|
||||||
|
_total_tasks = _sub_tasks_count + 1; -- +1 for the parent task
|
||||||
|
|
||||||
|
IF _total_tasks = 0 THEN
|
||||||
|
_ratio = 0;
|
||||||
|
ELSE
|
||||||
|
_ratio = (_total_completed / _total_tasks) * 100;
|
||||||
|
END IF;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Technical Implementation Details
|
||||||
|
|
||||||
|
The progress calculation logic is implemented in PostgreSQL functions, primarily in the `get_task_complete_ratio` function. Progress updates flow through the system as follows:
|
||||||
|
|
||||||
|
1. **User Action**: User updates task progress or weight in the UI
|
||||||
|
2. **Socket Event**: Client emits socket event (UPDATE_TASK_PROGRESS or UPDATE_TASK_WEIGHT)
|
||||||
|
3. **Server Handler**: Server processes the event in the respective handler function
|
||||||
|
4. **Database Update**: Progress/weight value is updated in the database
|
||||||
|
5. **Recalculation**: If needed, parent task progress is recalculated
|
||||||
|
6. **Broadcast**: Changes are broadcast to all clients in the project room
|
||||||
|
7. **UI Update**: Client UI updates to reflect the new progress values
|
||||||
|
|
||||||
|
This architecture allows for real-time updates and consistent progress calculation across all clients.
|
||||||
|
|
||||||
|
## Associated Files and Components
|
||||||
|
|
||||||
|
### Backend Files
|
||||||
|
|
||||||
|
1. **Socket Event Handlers**:
|
||||||
|
- `worklenz-backend/src/socket.io/commands/on-update-task-progress.ts` - Handles manual progress updates
|
||||||
|
- `worklenz-backend/src/socket.io/commands/on-update-task-weight.ts` - Handles task weight updates
|
||||||
|
|
||||||
|
2. **Database Functions**:
|
||||||
|
- `worklenz-backend/database/migrations/20250423000000-subtask-manual-progress.sql` - Contains the `get_task_complete_ratio` function that calculates progress based on the selected method
|
||||||
|
- Functions that support project creation/updates with progress mode settings:
|
||||||
|
- `create_project`
|
||||||
|
- `update_project`
|
||||||
|
|
||||||
|
3. **Controllers**:
|
||||||
|
- `worklenz-backend/src/controllers/project-workload/workload-gannt-base.ts` - Contains the `calculateTaskCompleteRatio` method
|
||||||
|
- `worklenz-backend/src/controllers/projects-controller.ts` - Handles project-level progress calculations
|
||||||
|
- `worklenz-backend/src/controllers/tasks-controller-base.ts` - Handles task progress calculation and updates task view models
|
||||||
|
|
||||||
|
### Frontend Files
|
||||||
|
|
||||||
|
1. **Project Configuration**:
|
||||||
|
- `worklenz-frontend/src/components/projects/project-drawer/project-drawer.tsx` - Contains UI for selecting progress method when creating/editing projects
|
||||||
|
|
||||||
|
2. **Progress Visualization Components**:
|
||||||
|
- `worklenz-frontend/src/components/project-list/project-list-table/project-list-progress/progress-list-progress.tsx` - Displays project progress
|
||||||
|
- `worklenz-frontend/src/pages/projects/project-view-1/taskList/taskListTable/taskListTableCells/TaskProgress.tsx` - Displays task progress
|
||||||
|
- `worklenz-frontend/src/pages/projects/projectView/taskList/task-list-table/task-list-table-cells/task-list-progress-cell/task-list-progress-cell.tsx` - Alternative task progress cell
|
||||||
|
- `worklenz-frontend/src/components/task-list-common/task-row/task-row-progress/task-row-progress.tsx` - Displays progress in task rows
|
||||||
|
|
||||||
|
3. **Progress Input Components**:
|
||||||
|
- `worklenz-frontend/src/components/task-drawer/shared/info-tab/details/task-drawer-progress/task-drawer-progress.tsx` - Component for inputting task progress/weight
|
||||||
|
|
||||||
|
## Choosing the Right Progress Method
|
||||||
|
|
||||||
|
Each progress method is suitable for different types of projects:
|
||||||
|
|
||||||
|
- **Manual Progress**: Best for creative work where progress is subjective
|
||||||
|
- **Weighted Progress**: Ideal for projects where some tasks are more significant than others
|
||||||
|
- **Time-based Progress**: Perfect for projects where time estimates are reliable and important
|
||||||
|
|
||||||
|
Project managers can choose the appropriate method when creating or editing a project in the project drawer, based on their team's workflow and project requirements.
|
||||||
@@ -198,6 +198,7 @@ export default class TasksControllerV2 extends TasksControllerBase {
|
|||||||
(SELECT use_manual_progress FROM projects WHERE id = t.project_id) AS project_use_manual_progress,
|
(SELECT use_manual_progress FROM projects WHERE id = t.project_id) AS project_use_manual_progress,
|
||||||
(SELECT use_weighted_progress FROM projects WHERE id = t.project_id) AS project_use_weighted_progress,
|
(SELECT use_weighted_progress FROM projects WHERE id = t.project_id) AS project_use_weighted_progress,
|
||||||
(SELECT use_time_progress FROM projects WHERE id = t.project_id) AS project_use_time_progress,
|
(SELECT use_time_progress FROM projects WHERE id = t.project_id) AS project_use_time_progress,
|
||||||
|
(SELECT (get_task_complete_ratio(t.id)).ratio) AS complete_ratio,
|
||||||
|
|
||||||
(SELECT phase_id FROM task_phase WHERE task_id = t.id) AS phase_id,
|
(SELECT phase_id FROM task_phase WHERE task_id = t.id) AS phase_id,
|
||||||
(SELECT name
|
(SELECT name
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export async function on_update_task_progress(io: any, socket: Socket, data: str
|
|||||||
|
|
||||||
// Get the current progress value to log the change
|
// Get the current progress value to log the change
|
||||||
const currentProgressResult = await db.query(
|
const currentProgressResult = await db.query(
|
||||||
"SELECT progress_value, project_id, FROM tasks WHERE id = $1",
|
"SELECT progress_value, project_id FROM tasks WHERE id = $1",
|
||||||
[task_id]
|
[task_id]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -70,24 +70,8 @@ export async function on_update_task_progress(io: any, socket: Socket, data: str
|
|||||||
|
|
||||||
console.log(`Emitted progress update for task ${task_id} to project room ${projectId}`);
|
console.log(`Emitted progress update for task ${task_id} to project room ${projectId}`);
|
||||||
|
|
||||||
// If this is a subtask, update the parent task's progress
|
// Recursively update all ancestors in the task hierarchy
|
||||||
if (parent_task_id) {
|
await updateTaskAncestors(io, projectId, parent_task_id);
|
||||||
const progressRatio = await db.query(
|
|
||||||
"SELECT get_task_complete_ratio($1) as ratio",
|
|
||||||
[parent_task_id]
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(`Updated parent task ${parent_task_id} progress: ${progressRatio?.rows[0]?.ratio}`);
|
|
||||||
|
|
||||||
// Emit the parent task's updated progress
|
|
||||||
io.to(projectId).emit(
|
|
||||||
SocketEvents.TASK_PROGRESS_UPDATED.toString(),
|
|
||||||
{
|
|
||||||
task_id: parent_task_id,
|
|
||||||
progress_value: progressRatio?.rows[0]?.ratio
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify that project updates are available
|
// Notify that project updates are available
|
||||||
notifyProjectUpdates(socket, task_id);
|
notifyProjectUpdates(socket, task_id);
|
||||||
@@ -95,4 +79,49 @@ export async function on_update_task_progress(io: any, socket: Socket, data: str
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
log_error(error);
|
log_error(error);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively updates all ancestor tasks' progress when a subtask changes
|
||||||
|
* @param io Socket.io instance
|
||||||
|
* @param projectId Project ID for room broadcasting
|
||||||
|
* @param taskId The task ID to update (starts with the parent task)
|
||||||
|
*/
|
||||||
|
async function updateTaskAncestors(io: any, projectId: string, taskId: string | null) {
|
||||||
|
if (!taskId) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get the current task's progress ratio
|
||||||
|
const progressRatio = await db.query(
|
||||||
|
"SELECT get_task_complete_ratio($1) as ratio",
|
||||||
|
[taskId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const ratio = progressRatio?.rows[0]?.ratio;
|
||||||
|
console.log(`Updated task ${taskId} progress: ${ratio}`);
|
||||||
|
|
||||||
|
// Emit the updated progress
|
||||||
|
io.to(projectId).emit(
|
||||||
|
SocketEvents.TASK_PROGRESS_UPDATED.toString(),
|
||||||
|
{
|
||||||
|
task_id: taskId,
|
||||||
|
progress_value: ratio
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Find this task's parent to continue the recursive update
|
||||||
|
const parentResult = await db.query(
|
||||||
|
"SELECT parent_task_id FROM tasks WHERE id = $1",
|
||||||
|
[taskId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const parentTaskId = parentResult.rows[0]?.parent_task_id;
|
||||||
|
|
||||||
|
// If there's a parent, recursively update it
|
||||||
|
if (parentTaskId) {
|
||||||
|
await updateTaskAncestors(io, projectId, parentTaskId);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
log_error(`Error updating ancestor task ${taskId}: ${error}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -572,14 +572,29 @@ const taskSlice = createSlice({
|
|||||||
) => {
|
) => {
|
||||||
const { taskId, progress, totalTasksCount, completedCount } = action.payload;
|
const { taskId, progress, totalTasksCount, completedCount } = action.payload;
|
||||||
|
|
||||||
for (const group of state.taskGroups) {
|
// Helper function to find and update a task at any nesting level
|
||||||
const task = group.tasks.find(task => task.id === taskId);
|
const findAndUpdateTask = (tasks: IProjectTask[]) => {
|
||||||
if (task) {
|
for (const task of tasks) {
|
||||||
task.complete_ratio = progress;
|
if (task.id === taskId) {
|
||||||
task.total_tasks_count = totalTasksCount;
|
task.complete_ratio = progress;
|
||||||
task.completed_count = completedCount;
|
task.total_tasks_count = totalTasksCount;
|
||||||
break;
|
task.completed_count = completedCount;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check subtasks if they exist
|
||||||
|
if (task.sub_tasks && task.sub_tasks.length > 0) {
|
||||||
|
const found = findAndUpdateTask(task.sub_tasks);
|
||||||
|
if (found) return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to find and update the task in any task group
|
||||||
|
for (const group of state.taskGroups) {
|
||||||
|
const found = findAndUpdateTask(group.tasks);
|
||||||
|
if (found) break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,54 @@
|
|||||||
|
import React from 'react';
|
||||||
import { Progress, Tooltip } from 'antd';
|
import { Progress, Tooltip } from 'antd';
|
||||||
import './task-list-progress-cell.css';
|
import './task-list-progress-cell.css';
|
||||||
import { IProjectTask } from '@/types/project/projectTasksViewModel.types';
|
import { IProjectTask } from '@/types/project/projectTasksViewModel.types';
|
||||||
|
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||||
|
|
||||||
type TaskListProgressCellProps = {
|
type TaskListProgressCellProps = {
|
||||||
task: IProjectTask;
|
task: IProjectTask;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TaskListProgressCell = ({ task }: TaskListProgressCellProps) => {
|
const TaskListProgressCell = ({ task }: TaskListProgressCellProps) => {
|
||||||
return task.is_sub_task ? null : (
|
const { project } = useAppSelector(state => state.projectReducer);
|
||||||
<Tooltip title={`${task.completed_count || 0} / ${task.total_tasks_count || 0}`}>
|
const isManualProgressEnabled = project?.use_manual_progress;
|
||||||
|
const isSubtask = task.is_sub_task;
|
||||||
|
const hasManualProgress = task.manual_progress;
|
||||||
|
|
||||||
|
// Handle different cases:
|
||||||
|
// 1. For subtasks when manual progress is enabled, show the progress
|
||||||
|
// 2. For parent tasks, always show progress
|
||||||
|
// 3. For subtasks when manual progress is not enabled, don't show progress (null)
|
||||||
|
|
||||||
|
if (isSubtask && !isManualProgressEnabled) {
|
||||||
|
return null; // Don't show progress for subtasks when manual progress is disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// For parent tasks, show completion ratio with task count tooltip
|
||||||
|
if (!isSubtask) {
|
||||||
|
return (
|
||||||
|
<Tooltip title={`${task.completed_count || 0} / ${task.total_tasks_count || 0}`}>
|
||||||
|
<Progress
|
||||||
|
percent={task.complete_ratio || 0}
|
||||||
|
type="circle"
|
||||||
|
size={24}
|
||||||
|
style={{ cursor: 'default' }}
|
||||||
|
strokeWidth={(task.complete_ratio || 0) >= 100 ? 9 : 7}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For subtasks with manual progress enabled, show the progress
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
title={hasManualProgress ? `Manual: ${task.progress_value || 0}%` : `${task.progress || 0}%`}
|
||||||
|
>
|
||||||
<Progress
|
<Progress
|
||||||
percent={task.complete_ratio || 0}
|
percent={hasManualProgress ? (task.progress_value || 0) : (task.progress || 0)}
|
||||||
type="circle"
|
type="circle"
|
||||||
size={24}
|
size={22} // Slightly smaller for subtasks
|
||||||
style={{ cursor: 'default' }}
|
style={{ cursor: 'default' }}
|
||||||
strokeWidth={(task.complete_ratio || 0) >= 100 ? 9 : 7}
|
strokeWidth={(task.progress || 0) >= 100 ? 9 : 7}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export interface ITaskStatusCategory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IProjectTask {
|
export interface IProjectTask {
|
||||||
|
manual_progress: any;
|
||||||
due_time?: string;
|
due_time?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user