ManagementSystem/FRONTEND/src/pages/Allocation/Allocation.tsx

477 lines
18 KiB
TypeScript

import { getAllUserDoing } from '@/api/Admin'
import { get } from '@/rtk/helpers/apiService'
import {
Avatar,
Badge,
Box,
Card,
Loader,
Modal,
Popover,
Text,
Tooltip,
} from '@mantine/core'
import { IconInnerShadowTopRightFilled } from '@tabler/icons-react'
import { useEffect, useState } from 'react'
import classes from './Allocation.module.css'
interface UserInfo {
self: string
accountId: string
avatarUrls: AvatarUrls
displayName: string
active: boolean
timeZone: string
accountType: string
}
interface AvatarUrls {
'48x48': string
'24x24': string
'16x16': string
'32x32': string
}
interface Issue {
expand: string
id: string
self: string
key: string
changelog: any
fields: IssueFields
}
interface IssueFields {
summary: string
timespent: number
timeoriginalestimate: number
project: ProjectInfo
status: StatusInfo
updated: string
}
interface ProjectInfo {
self: string
id: string
key: string
name: string
projectTypeKey: string
simplified: boolean
avatarUrls: AvatarUrls
}
interface StatusCategory {
self: string
id: number
key: string
colorName: string
name: string
}
interface StatusInfo {
self: string
description: string
iconUrl: string
name: string
id: string
statusCategory: StatusCategory
}
const Allocation = () => {
const [loading, setLoading] = useState(true)
const [opened, setOpened] = useState(false)
const [issDetail, setIssDetail] = useState('')
const [data, setData] = useState<any>({})
const getAll = async () => {
try {
const res = await get(getAllUserDoing)
if (res.status) {
setLoading(false)
setData(res.data)
}
} catch (error) {
console.log(error)
}
}
// console.log(data)
const getStateChanges = (changelog: any) => {
return changelog.histories
.map((history: any) => {
const author = history.author.displayName
const created = history.created
const changes = history.items
.map((item: any) => {
return `${item.field} changed from ${item.fromString}${item.toString}`
})
.join(', ')
return changes
? `${author} made changes on ${created}: ${changes}`
: null
})
.filter(Boolean)
.join('\n')
}
useEffect(() => {
getAll()
}, [])
return (
<div>
<div>
<Box
style={{
marginTop: '20%',
textAlign: 'center',
display: loading ? 'block' : 'none',
// display: 'none',
}}
>
<Loader size={'sm'} color="grape" type="bars" m={'0 auto'} />
<Text fw={600} c={'gray'}>
Analyzing . . .
</Text>
</Box>
<Box
style={{
display: !loading ? 'flex' : 'none',
flexFlow: 'column',
}}
>
<Box>
<Text fw={600} fz={'md'}>
Admin/
</Text>
<Text fw={700} fz={'lg'}>
Personnel Allocation
</Text>
<Text fw={600} fz={'sm'} c={'gray'} fs={'italic'} ml={'md'}>
"P:" is the timspent/timeestimate number within the project itself
</Text>
<Text fw={600} fz={'sm'} c={'gray'} fs={'italic'} ml={'md'}>
"A:" is the timspent/timeestimate of all projects
</Text>
</Box>
<Box
style={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-evenly',
}}
>
{!loading &&
Object.keys(data?.projects).map((pro) => {
return (
<Box w={'32%'} mt={'xl'}>
<Card
shadow="md"
padding="xl"
component="a"
target="_blank"
style={{
minHeight: '28vh',
border: 'solid 1px rgb(28 74 115 / 64%)',
}}
>
<Card.Section
style={{
display: 'flex',
alignItems: 'center',
padding: '5px 20px',
backgroundColor: '#1c4a73',
color: 'white',
}}
>
<Avatar
size={'sm'}
src={data.projects[pro].project.avatarUrls['48x48']}
/>
<Text ml={'md'} fw={700} size="lg">
{pro}
</Text>
</Card.Section>
{!loading &&
Object.keys(data?.projects[pro].users).map((user) => {
const userData = data.projects[pro].users[user]
return (
<Box style={{ cursor: 'pointer' }}>
<Popover
width={'25%'}
position="bottom"
withArrow
shadow="md"
>
<Popover.Target>
<Box
mt={'sm'}
style={{
display: 'flex',
alignItems: 'center',
}}
>
<Box
style={{
display: 'flex',
width: '60%',
backgroundColor:
userData.issues?.filter(
(iss: Issue) =>
iss.fields.status.name ===
'In Progress',
)?.length === 0
? 'yellow'
: '',
alignItems: 'center',
}}
>
<Box w='10%'>
<IconInnerShadowTopRightFilled
style={{ color: 'orange' }}
height={20}
width={20}
display={userData.issues?.filter(
(iss: Issue) =>
iss.fields.status.name === 'In Progress' &&
((Date.now() - (new Date(iss.changelog?.histories[0]?.created)).getTime()) > 172800000)
).length > 0 ? 'block' :'none'}
/>
</Box>
<Box display={'flex'}>
<Avatar
size={'sm'}
ml={'5px'}
src={userData.user.avatarUrls['48x48']}
/>
<Text ml={'md'} fw={600}>
{user}
</Text>
</Box>
</Box>
<Box
display={'flex'}
style={{
backgroundColor:
userData.p_total_spent >
userData.p_total_est
? '#ff960066'
: '',
justifyContent: 'center',
width: '20%',
}}
ml={'md'}
p="0 20px"
>
<Text ml={'md'} fw={700} fz={'sm'}>{`P: `}</Text>
<Text fw={700} c="green" fz={'sm'}>{`${
userData.p_total_spent / 60 / 60
}h/`}</Text>
<Text fw={700} c="blue" fz={'sm'}>{`${
userData.p_total_est / 60 / 60
}h`}</Text>
</Box>
<Box
display={'flex'}
style={{
backgroundColor:
data.users[user].total_spent >
data.users[user].total_est
? '#ff960066'
: '',
justifyContent: 'center',
width: '20%',
}}
ml={'md'}
p="0 20px"
>
<Text fw={700} fz={'sm'}>{`A: `}</Text>
<Text fw={700} c="green" fz={'sm'}>{`${
data.users[user].total_spent / 60 / 60
}h/`}</Text>
<Text fw={700} c="blue" fz={'sm'}>{`${
data.users[user].total_est / 60 / 60
}h`}</Text>
</Box>
</Box>
</Popover.Target>
<Popover.Dropdown>
<Text
size="xs"
style={{
maxHeight: '50vh',
overflow: 'auto',
}}
>
{userData.issues?.map((iss: Issue) => {
const date = new Date(iss.fields.updated)
const issStatus =
iss.fields.status.name === 'In Progress'
const issLastHistory =
Date.now() -
new Date(
iss.changelog?.histories[0]?.created,
).getTime()
return (
<Tooltip label="Click to view history">
<Box
className={
Date.now() - date.getTime() >
172800000 * 5
? classes['blinking-background']
: ''
}
style={{
margin: '10px 0',
padding: '5px',
borderRadius: '10px',
cursor: 'pointer',
backgroundColor: issStatus
? issLastHistory < 172800000
? '#d1f3d1'
: '#ffff8a'
: '',
}}
onClick={async () => {
setIssDetail(
getStateChanges(iss.changelog),
)
setOpened(true)
}}
>
<Text fw={600}>
<a
href={
'https://apactechvn.atlassian.net/browse/' +
iss.key
}
target="_blank"
>
{iss.key}
</a>
: {iss.fields.summary}
</Text>
<Text
fw={600}
ml={'50px'}
c={
iss.fields.status.statusCategory
.colorName
}
>
Status: {iss.fields.status.name}
</Text>
<Text fw={600} ml={'50px'}>
Time spent:{' '}
<Badge size="xs" bg={'orange'}>
{iss.fields.timespent / 60 / 60}
h
</Badge>
</Text>
<Text fw={600} ml={'50px'}>
EST:{' '}
<Badge size="xs">
{iss.fields
.timeoriginalestimate /
60 /
60}
h
</Badge>
</Text>
<Text fw={600} ml={'50px'}>
Updated:{' '}
<Badge size="xs">
{iss.fields.updated}
</Badge>
</Text>
</Box>
</Tooltip>
)
})}
</Text>
</Popover.Dropdown>
</Popover>
</Box>
)
})}
</Card>
</Box>
)
})}
<Box
w={'32%'}
mt={'xl'}
style={{
display: data?.warningList?.length > 0 ? 'block' : 'none',
}}
>
<Card
shadow="md"
padding="xl"
component="a"
target="_blank"
style={{
minHeight: '28vh',
border: 'solid 1px rgb(28 74 115 / 64%)',
}}
>
<Card.Section
style={{
display: 'flex',
alignItems: 'center',
padding: '5px 20px',
backgroundColor: '#ffa200',
color: 'white',
}}
className={classes['blinking-background']}
>
<Avatar
size={'sm'}
src={
'https://cdn3.iconfinder.com/data/icons/toolbar-people/512/user_error_man_male_profile_warning-512.png'
}
/>
<Text ml={'md'} fw={700} size="lg">
WARNING - NO ASSIGNMENT
</Text>
</Card.Section>
{!loading &&
data?.warningList?.map((user: UserInfo) => {
return (
<Box style={{ cursor: 'pointer' }}>
<Box
mt={'sm'}
style={{
display: 'flex',
alignItems: 'center',
marginLeft: '30px',
}}
>
<Avatar size={'sm'} src={user.avatarUrls['48x48']} />
<Text ml={'md'} fw={600} w={'40%'}>
{user.displayName}
</Text>
</Box>
</Box>
)
})}
</Card>
</Box>
</Box>
</Box>
</div>
<Modal
title={<b>HISTORY</b>}
onClose={() => setOpened(false)}
opened={opened}
fullScreen
>
<pre>{issDetail}</pre>
</Modal>
</div>
)
}
export default Allocation