Files
worklenz/worklenz-frontend/src/components/task-drawer/task-drawer.tsx
chamiakJ 6128c64c31 Add task progress tracking methods and enhance UI components
- Introduced a comprehensive guide for users on task progress tracking methods, including manual, weighted, and time-based progress.
- Implemented backend support for progress calculations, including SQL functions and migrations to accommodate new progress features.
- Enhanced frontend components to support progress input and display, including updates to task and project drawers.
- Added localization for new progress-related terms and validation messages.
- Integrated real-time updates for task progress and weight changes through socket events.
2025-04-30 15:24:07 +05:30

234 lines
6.4 KiB
TypeScript

import { TabsProps, Tabs, Button } from 'antd';
import Drawer from 'antd/es/drawer';
import { InputRef } from 'antd/es/input';
import { useTranslation } from 'react-i18next';
import { useEffect, useRef, useState } from 'react';
import { PlusOutlined } from '@ant-design/icons';
import { useAppSelector } from '@/hooks/useAppSelector';
import { useAppDispatch } from '@/hooks/useAppDispatch';
import {
setSelectedTaskId,
setShowTaskDrawer,
setTaskFormViewModel,
setTaskSubscribers,
setTimeLogEditing,
} from '@/features/task-drawer/task-drawer.slice';
import './task-drawer.css';
import TaskDrawerHeader from './task-drawer-header/task-drawer-header';
import TaskDrawerActivityLog from './shared/activity-log/task-drawer-activity-log';
import TaskDrawerInfoTab from './shared/info-tab/task-drawer-info-tab';
import TaskDrawerTimeLog from './shared/time-log/task-drawer-time-log';
import TimeLogForm from './shared/time-log/time-log-form';
import { DEFAULT_TASK_NAME } from '@/shared/constants';
import useTaskDrawerUrlSync from '@/hooks/useTaskDrawerUrlSync';
import InfoTabFooter from './shared/info-tab/info-tab-footer';
import { Flex } from 'antd';
const TaskDrawer = () => {
const { t } = useTranslation('task-drawer/task-drawer');
const [activeTab, setActiveTab] = useState<string>('info');
const [refreshTimeLogTrigger, setRefreshTimeLogTrigger] = useState(0);
const { showTaskDrawer, timeLogEditing } = useAppSelector(state => state.taskDrawerReducer);
const { taskFormViewModel, selectedTaskId } = useAppSelector(state => state.taskDrawerReducer);
const taskNameInputRef = useRef<InputRef>(null);
const isClosingManually = useRef(false);
// Use the custom hook to sync the task drawer state with the URL
const { clearTaskFromUrl } = useTaskDrawerUrlSync();
useEffect(() => {
if (taskNameInputRef.current?.input?.value === DEFAULT_TASK_NAME) {
taskNameInputRef.current.focus();
}
}, [showTaskDrawer]);
const dispatch = useAppDispatch();
const resetTaskState = () => {
dispatch(setShowTaskDrawer(false));
dispatch(setSelectedTaskId(null));
dispatch(setTaskFormViewModel({}));
dispatch(setTaskSubscribers([]));
};
const handleOnClose = (
e?: React.MouseEvent<Element, MouseEvent> | React.KeyboardEvent<Element>
) => {
// Set flag to indicate we're manually closing the drawer
isClosingManually.current = true;
setActiveTab('info');
clearTaskFromUrl();
const isClickOutsideDrawer =
e?.target && (e.target as HTMLElement).classList.contains('ant-drawer-mask');
if (isClickOutsideDrawer || !taskFormViewModel?.task?.is_sub_task) {
resetTaskState();
} else {
dispatch(setSelectedTaskId(null));
dispatch(setTaskFormViewModel({}));
dispatch(setTaskSubscribers([]));
dispatch(setSelectedTaskId(taskFormViewModel?.task?.parent_task_id || null));
}
// Reset the flag after a short delay
setTimeout(() => {
isClosingManually.current = false;
}, 100);
};
const handleTabChange = (key: string) => {
setActiveTab(key);
};
const handleCancelTimeLog = () => {
dispatch(
setTimeLogEditing({
isEditing: false,
logBeingEdited: null,
})
);
};
const handleAddTimeLog = () => {
dispatch(
setTimeLogEditing({
isEditing: true,
logBeingEdited: null,
})
);
};
// Function to trigger a refresh of the time log list
const refreshTimeLogs = () => {
setRefreshTimeLogTrigger(prev => prev + 1);
};
const handleTimeLogSubmitSuccess = () => {
// Close the form
handleCancelTimeLog();
// Trigger refresh of time logs
refreshTimeLogs();
};
const tabItems: TabsProps['items'] = [
{
key: 'info',
label: t('taskInfoTab.title'),
children: <TaskDrawerInfoTab t={t} />,
},
{
key: 'timeLog',
label: t('taskTimeLogTab.title'),
children: <TaskDrawerTimeLog t={t} refreshTrigger={refreshTimeLogTrigger} />,
},
{
key: 'activityLog',
label: t('taskActivityLogTab.title'),
children: <TaskDrawerActivityLog />,
},
];
// Render the appropriate footer based on the active tab
const renderFooter = () => {
if (activeTab === 'info') {
return <InfoTabFooter />;
} else if (activeTab === 'timeLog') {
if (timeLogEditing.isEditing) {
return (
<TimeLogForm
onCancel={handleCancelTimeLog}
onSubmitSuccess={handleTimeLogSubmitSuccess}
initialValues={timeLogEditing.logBeingEdited || undefined}
mode={timeLogEditing.logBeingEdited ? 'edit' : 'create'}
/>
);
} else {
return (
<Flex justify="center" style={{ width: '100%', padding: '16px 0 0' }}>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={handleAddTimeLog}
style={{ width: '100%' }}
>
Add new time log
</Button>
</Flex>
);
}
}
return null;
};
// Create conditional footer styles based on active tab
const getFooterStyle = () => {
const baseStyle = {
padding: '0 24px 16px',
width: '100%',
height: 'auto',
boxSizing: 'border-box' as const,
};
if (activeTab === 'timeLog') {
return {
...baseStyle,
overflow: 'visible', // Remove scrolling for timeLog tab
};
}
return {
...baseStyle,
overflow: 'hidden',
};
};
// Get conditional body style
const getBodyStyle = () => {
const baseStyle = {
padding: '24px',
overflow: 'auto',
};
if (activeTab === 'timeLog' && timeLogEditing.isEditing) {
return {
...baseStyle,
height: 'calc(100% - 220px)', // More space for the timeLog form
};
}
return {
...baseStyle,
height: 'calc(100% - 180px)',
};
};
const drawerProps = {
open: showTaskDrawer,
onClose: handleOnClose,
width: 720,
style: { justifyContent: 'space-between' },
destroyOnClose: true,
title: <TaskDrawerHeader inputRef={taskNameInputRef} t={t} />,
footer: renderFooter(),
bodyStyle: getBodyStyle(),
footerStyle: getFooterStyle(),
};
return (
<Drawer {...drawerProps}>
<Tabs
type="card"
items={tabItems}
destroyInactiveTabPane
onChange={handleTabChange}
activeKey={activeTab}
/>
</Drawer>
);
};
export default TaskDrawer;