init
This commit is contained in:
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user