This commit is contained in:
chamikaJ
2025-04-17 18:28:54 +05:30
parent f583291d8a
commit 8825b0410a
2837 changed files with 241385 additions and 127578 deletions

View File

@@ -0,0 +1,131 @@
import { useEffect, useState } from 'react';
import { ConfigProvider, Table, TableColumnsType } from 'antd';
import { useTranslation } from 'react-i18next';
import { useAppDispatch } from '@/hooks/useAppDispatch';
import CustomTableTitle from '@/components/CustomTableTitle';
import TasksProgressCell from './tablesCells/tasksProgressCell/TasksProgressCell';
import MemberCell from './tablesCells/memberCell/MemberCell';
import { fetchMembersData, toggleMembersReportsDrawer } from '@/features/reporting/membersReports/membersReportsSlice';
import { useAppSelector } from '@/hooks/useAppSelector';
import MembersReportsDrawer from '@/features/reporting/membersReports/membersReportsDrawer/members-reports-drawer';
const MembersReportsTable = () => {
const { t } = useTranslation('reporting-members');
const dispatch = useAppDispatch();
const [selectedId, setSelectedId] = useState<string | null>(null);
const { duration, dateRange } = useAppSelector(state => state.reportingReducer);
const { membersList, isLoading, total, archived, searchQuery } = useAppSelector(state => state.membersReportsReducer);
// function to handle drawer toggle
const handleDrawerOpen = (id: string) => {
setSelectedId(id);
dispatch(toggleMembersReportsDrawer());
};
const columns: TableColumnsType = [
{
key: 'member',
title: <CustomTableTitle title={t('memberColumn')} />,
onCell: record => {
return {
onClick: () => handleDrawerOpen(record.id),
};
},
render: record => <MemberCell member={record} />,
},
{
key: 'tasksProgress',
title: <CustomTableTitle title={t('tasksProgressColumn')} />,
render: record => {
const { todo, doing, done } = record.tasks_stat;
return (todo || doing || done) ? <TasksProgressCell tasksStat={record.tasks_stat} /> : '-';
},
},
{
key: 'tasksAssigned',
title: (
<CustomTableTitle
title={t('tasksAssignedColumn')}
tooltip={t('tasksAssignedColumnTooltip')}
/>
),
className: 'text-center group-hover:text-[#1890ff]',
dataIndex: 'tasks',
width: 180,
},
{
key: 'overdueTasks',
title: (
<CustomTableTitle
title={t('overdueTasksColumn')}
tooltip={t('overdueTasksColumnTooltip')}
/>
),
className: 'text-center group-hover:text-[#1890ff]',
dataIndex: 'overdue',
width: 180,
},
{
key: 'completedTasks',
title: (
<CustomTableTitle
title={t('completedTasksColumn')}
tooltip={t('completedTasksColumnTooltip')}
/>
),
className: 'text-center group-hover:text-[#1890ff]',
dataIndex: 'completed',
width: 180,
},
{
key: 'ongoingTasks',
title: (
<CustomTableTitle
title={t('ongoingTasksColumn')}
tooltip={t('ongoingTasksColumnTooltip')}
/>
),
className: 'text-center group-hover:text-[#1890ff]',
dataIndex: 'ongoing',
width: 180,
},
];
useEffect(() => {
if (!isLoading) dispatch(fetchMembersData({ duration, dateRange }));
}, [dispatch, archived, searchQuery, dateRange]);
return (
<ConfigProvider
theme={{
components: {
Table: {
cellPaddingBlock: 8,
cellPaddingInline: 10,
},
},
}}
>
<Table
columns={columns}
dataSource={membersList}
rowKey={record => record.id}
pagination={{ showSizeChanger: true, defaultPageSize: 10, total: total }}
scroll={{ x: 'max-content' }}
loading={isLoading}
onRow={record => {
return {
style: { height: 48, cursor: 'pointer' },
className: 'group even:bg-[#4e4e4e10]',
};
}}
/>
<MembersReportsDrawer memberId={selectedId} />
</ConfigProvider>
);
};
export default MembersReportsTable;

View File

@@ -0,0 +1,28 @@
import { Avatar, Flex, Typography } from 'antd';
import CustomAvatar from '@components/CustomAvatar';
type ProjectMangerCellProps = {
member: { avatar_url: string; name: string } | null;
};
const MemberCell = ({ member }: ProjectMangerCellProps) => {
return (
<div>
{member ? (
<Flex gap={8} align="center">
{member?.avatar_url ? (
<Avatar src={member.avatar_url} />
) : (
<CustomAvatar avatarName={member.name} />
)}
<Typography.Text className="group-hover:text-[#1890ff]">{member.name}</Typography.Text>
</Flex>
) : (
<Typography.Text className="group-hover:text-[#1890ff]">-</Typography.Text>
)}
</div>
);
};
export default MemberCell;

View File

@@ -0,0 +1,79 @@
import { Flex, Tooltip, Typography } from 'antd';
import { colors } from '@/styles/colors';
import { useTranslation } from 'react-i18next';
type TasksProgressCellProps = {
tasksStat: { todo: number; doing: number; done: number } | null;
};
const TasksProgressCell = ({ tasksStat }: TasksProgressCellProps) => {
// localization
const { t } = useTranslation('reporting-members');
if (!tasksStat) return null;
const totalStat = tasksStat.todo + tasksStat.doing + tasksStat.done;
if (totalStat === 0) return null;
const todoPercent = Math.round((tasksStat.todo / totalStat) * 100);
const doingPercent = Math.round((tasksStat.doing / totalStat) * 100);
const donePercent = Math.round((tasksStat.done / totalStat) * 100);
const segments = [
{ percent: donePercent, color: '#98d4b1', label: 'done' },
{ percent: doingPercent, color: '#bce3cc', label: 'doing' },
{ percent: todoPercent, color: '#e3f4ea', label: 'todo' },
];
return (
<Tooltip
trigger={'hover'}
title={
<Flex vertical>
{segments.map((seg, index) => (
<Typography.Text
key={index}
style={{ color: colors.white }}
>{`${t(`${seg.label}Text`)}: ${seg.percent}%`}</Typography.Text>
))}
</Flex>
}
>
<Flex
align="center"
style={{
width: '100%',
maxWidth: 200,
height: 16,
borderRadius: 4,
overflow: 'hidden',
cursor: 'pointer',
}}
>
{segments.map(
(segment, index) =>
segment.percent > 0 && (
<Typography.Text
key={index}
ellipsis
style={{
textAlign: 'center',
fontSize: 10,
fontWeight: 500,
color: colors.darkGray,
padding: '2px 4px',
minWidth: 32,
flexBasis: `${segment.percent}%`,
backgroundColor: segment.color,
}}
>
{segment.percent}%
</Typography.Text>
)
)}
</Flex>
</Tooltip>
);
};
export default TasksProgressCell;

View File

@@ -0,0 +1,86 @@
import { Button, Card, Checkbox, Dropdown, Flex, Skeleton, Space, Typography } from 'antd';
import { DownOutlined } from '@ant-design/icons';
import MembersReportsTable from './members-reports-table/members-reports-table';
import TimeWiseFilter from '@/components/reporting/time-wise-filter';
import { useAppDispatch } from '@/hooks/useAppDispatch';
import { useAppSelector } from '@/hooks/useAppSelector';
import { useTranslation } from 'react-i18next';
import CustomSearchbar from '@components/CustomSearchbar';
import { useDocumentTitle } from '@/hooks/useDoumentTItle';
import CustomPageHeader from '../page-header/custom-page-header';
import {
fetchMembersData,
setArchived,
setDuration,
setDateRange,
setSearchQuery,
} from '@/features/reporting/membersReports/membersReportsSlice';
import { useAuthService } from '@/hooks/useAuth';
import { reportingExportApiService } from '@/api/reporting/reporting-export.api.service';
import { useEffect } from 'react';
const MembersReports = () => {
const { t } = useTranslation('reporting-members');
const dispatch = useAppDispatch();
useDocumentTitle('Reporting - Members');
const currentSession = useAuthService().getCurrentSession();
const { archived, searchQuery } = useAppSelector(
state => state.membersReportsReducer,
);
const { duration, dateRange } = useAppSelector(state => state.reportingReducer);
const handleExport = () => {
if (!currentSession?.team_name) return;
reportingExportApiService.exportMembers(currentSession.team_name, duration, dateRange, archived);
};
useEffect(() => {
dispatch(setDuration(duration));
dispatch(setDateRange(dateRange));
}, [dateRange, duration]);
return (
<Flex vertical>
<CustomPageHeader
title={`Members`}
children={
<Space>
<Button>
<Checkbox checked={archived} onChange={() => dispatch(setArchived(!archived))}>
<Typography.Text>{t('includeArchivedButton')}</Typography.Text>
</Checkbox>
</Button>
<TimeWiseFilter />
<Dropdown
menu={{ items: [{ key: '1', label: t('excelButton') }], onClick: handleExport }}
>
<Button type="primary" icon={<DownOutlined />} iconPosition="end">
{t('exportButton')}
</Button>
</Dropdown>
</Space>
}
/>
<Card
title={
<Flex justify="flex-end">
<CustomSearchbar
placeholderText={t('searchByNameInputPlaceholder')}
searchQuery={searchQuery}
setSearchQuery={query => dispatch(setSearchQuery(query))}
/>
</Flex>
}
>
<MembersReportsTable />
</Card>
</Flex>
);
};
export default MembersReports;