Merge branch 'feature/recurring-tasks' of https://github.com/Worklenz/worklenz into feature/project-finance

This commit is contained in:
chamikaJ
2025-05-22 08:11:27 +05:30
44 changed files with 2593 additions and 1098 deletions

View File

@@ -132,7 +132,7 @@ const RecentAndFavouriteProjectList = () => {
<div style={{ maxHeight: 420, overflow: 'auto' }}>
{projectsData?.body?.length === 0 ? (
<Empty
image="https://app.worklenz.com/assets/images/empty-box.webp"
image="https://s3.us-west-2.amazonaws.com/worklenz.com/assets/empty-box.webp"
imageStyle={{ height: 60 }}
style={{
display: 'flex',

View File

@@ -259,7 +259,7 @@ const TasksList: React.FC = React.memo(() => {
<Skeleton active />
) : data?.body.total === 0 ? (
<EmptyListPlaceholder
imageSrc="https://app.worklenz.com/assets/images/empty-box.webp"
imageSrc="https://s3.us-west-2.amazonaws.com/worklenz.com/assets/empty-box.webp"
text=" No tasks to show."
/>
) : (

View File

@@ -147,7 +147,7 @@ const TodoList = () => {
<div style={{ maxHeight: 420, overflow: 'auto' }}>
{data?.body.length === 0 ? (
<EmptyListPlaceholder
imageSrc="https://app.worklenz.com/assets/images/empty-box.webp"
imageSrc="https://s3.us-west-2.amazonaws.com/worklenz.com/assets/empty-box.webp"
text={t('home:todoList.noTasks')}
/>
) : (

View File

@@ -263,7 +263,7 @@ const ProjectViewMembers = () => {
>
{members?.total === 0 ? (
<EmptyListPlaceholder
imageSrc="https://app.worklenz.com/assets/images/empty-box.webp"
imageSrc="https://s3.us-west-2.amazonaws.com/worklenz.com/assets/empty-box.webp"
imageHeight={120}
text={t('emptyText')}
/>

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useMemo, useCallback } from 'react';
import { PushpinFilled, PushpinOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import { Badge, Button, ConfigProvider, Flex, Tabs, TabsProps, Tooltip } from 'antd';
import { useLocation, useNavigate, useParams, useSearchParams } from 'react-router-dom';
@@ -43,6 +43,14 @@ const ProjectView = () => {
const [pinnedTab, setPinnedTab] = useState<string>(searchParams.get('pinned_tab') || '');
const [taskid, setTaskId] = useState<string>(searchParams.get('task') || '');
const resetProjectData = useCallback(() => {
dispatch(setProjectId(null));
dispatch(resetStatuses());
dispatch(deselectAll());
dispatch(resetTaskListData());
dispatch(resetBoardData());
}, [dispatch]);
useEffect(() => {
if (projectId) {
dispatch(setProjectId(projectId));
@@ -59,9 +67,13 @@ const ProjectView = () => {
dispatch(setSelectedTaskId(taskid || ''));
dispatch(setShowTaskDrawer(true));
}
}, [dispatch, navigate, projectId, taskid]);
const pinToDefaultTab = async (itemKey: string) => {
return () => {
resetProjectData();
};
}, [dispatch, navigate, projectId, taskid, resetProjectData]);
const pinToDefaultTab = useCallback(async (itemKey: string) => {
if (!itemKey || !projectId) return;
const defaultView = itemKey === 'tasks-list' ? 'TASK_LIST' : 'BOARD';
@@ -88,9 +100,9 @@ const ProjectView = () => {
}).toString(),
});
}
};
}, [projectId, activeTab, navigate]);
const handleTabChange = (key: string) => {
const handleTabChange = useCallback((key: string) => {
setActiveTab(key);
dispatch(setProjectView(key === 'board' ? 'kanban' : 'list'));
navigate({
@@ -100,9 +112,9 @@ const ProjectView = () => {
pinned_tab: pinnedTab,
}).toString(),
});
};
}, [dispatch, location.pathname, navigate, pinnedTab]);
const tabMenuItems = tabItems.map(item => ({
const tabMenuItems = useMemo(() => tabItems.map(item => ({
key: item.key,
label: (
<Flex align="center" style={{ color: colors.skyBlue }}>
@@ -144,21 +156,17 @@ const ProjectView = () => {
</Flex>
),
children: item.element,
}));
})), [pinnedTab, pinToDefaultTab]);
const resetProjectData = () => {
dispatch(setProjectId(null));
dispatch(resetStatuses());
dispatch(deselectAll());
dispatch(resetTaskListData());
dispatch(resetBoardData());
};
useEffect(() => {
return () => {
resetProjectData();
};
}, []);
const portalElements = useMemo(() => (
<>
{createPortal(<ProjectMemberDrawer />, document.body, 'project-member-drawer')}
{createPortal(<PhaseDrawer />, document.body, 'phase-drawer')}
{createPortal(<StatusDrawer />, document.body, 'status-drawer')}
{createPortal(<TaskDrawer />, document.body, 'task-drawer')}
{createPortal(<DeleteStatusDrawer />, document.body, 'delete-status-drawer')}
</>
), []);
return (
<div style={{ marginBlockStart: 80, marginBlockEnd: 24, minHeight: '80vh' }}>
@@ -170,33 +178,11 @@ const ProjectView = () => {
items={tabMenuItems}
tabBarStyle={{ paddingInline: 0 }}
destroyInactiveTabPane={true}
// tabBarExtraContent={
// <div>
// <span style={{ position: 'relative', top: '-10px' }}>
// <Tooltip title="Members who are active on this project will be displayed here.">
// <QuestionCircleOutlined />
// </Tooltip>
// </span>
// <span
// style={{
// position: 'relative',
// right: '20px',
// top: '10px',
// }}
// >
// <Badge status="success" dot className="profile-badge" />
// </span>
// </div>
// }
/>
{createPortal(<ProjectMemberDrawer />, document.body, 'project-member-drawer')}
{createPortal(<PhaseDrawer />, document.body, 'phase-drawer')}
{createPortal(<StatusDrawer />, document.body, 'status-drawer')}
{createPortal(<TaskDrawer />, document.body, 'task-drawer')}
{createPortal(<DeleteStatusDrawer />, document.body, 'delete-status-drawer')}
{portalElements}
</div>
);
};
export default ProjectView;
export default React.memo(ProjectView);

View File

@@ -81,6 +81,22 @@ const MembersTimeSheet = forwardRef<MembersTimeSheetRef>((_, ref) => {
display: false,
position: 'top' as const,
},
tooltip: {
callbacks: {
label: function(context: any) {
const idx = context.dataIndex;
const member = jsonData[idx];
const hours = member?.utilized_hours || '0.00';
const percent = member?.utilization_percent || '0.00';
const overUnder = member?.over_under_utilized_hours || '0.00';
return [
`${context.dataset.label}: ${hours} h`,
`Utilization: ${percent}%`,
`Over/Under Utilized: ${overUnder} h`
];
}
}
}
},
backgroundColor: 'black',
indexAxis: 'y' as const,