ManagementSystem/FRONTEND/src/components/Navbar/Navbar.tsx

632 lines
18 KiB
TypeScript
Executable File

import { getQRCode } from '@/api/Admin'
import { changePassword } from '@/api/Auth'
import { logout } from '@/rtk/dispatches/auth'
import { get, post } from '@/rtk/helpers/apiService'
import { requirementsPassword } from '@/rtk/helpers/variables'
import { useAppDispatch, useAppSelector } from '@/rtk/hooks'
import {
Avatar,
Box,
Button,
Code,
Divider,
Group,
Modal,
PasswordInput,
Text,
TextInput,
Tooltip,
useComputedColorScheme,
useMantineColorScheme,
} from '@mantine/core'
import { notifications } from '@mantine/notifications'
import {
IconBinaryTree2,
IconCalendar,
IconCalendarClock,
IconChartDots2,
IconDevices,
IconLayoutSidebarLeftExpand,
IconLayoutSidebarRightExpand,
IconListCheck,
IconLogout,
// IconMail,
IconMoon,
IconQrcode,
IconReport,
IconScan,
IconSun,
IconTicket,
IconUsersGroup,
IconZoomExclamation
} from '@tabler/icons-react'
import { useCallback, useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import PasswordRequirementInput from '../PasswordRequirementInput/PasswordRequirementInput'
import classes from './NavbarSimpleColored.module.css'
const data = [
// { link: '/dashboard', label: 'Dashboard', icon: IconHome },
{
link: '/timekeeping',
label: 'Timekeeping',
icon: IconCalendar,
group: 'normal',
},
{
link: '/tracking',
label: 'Check in/out',
icon: IconScan,
group: 'admin',
},
{
link: '/worklogs',
label: 'Worklogs',
icon: IconReport,
group: 'normal',
},
{
link: '/leave-management',
label: 'Leave Management',
icon: IconCalendarClock,
group: 'normal',
},
{ link: '/tickets', label: 'Tickets', icon: IconTicket, group: 'normal' },
{
link: '/tickets-management',
label: 'Tickets Management',
icon: IconDevices,
group: 'admin',
},
{
link: '/users',
label: 'Users Management',
icon: IconUsersGroup,
group: 'admin',
},
{
link: '/sprint-review',
label: 'Sprint Review',
icon: IconListCheck,
group: 'admin',
},
{
link: '/test-report',
label: 'Test Report',
icon: IconZoomExclamation,
group: 'admin',
},
{
link: '/allocation',
label: 'Personnel allocation',
icon: IconBinaryTree2,
group: 'normal',
},
{
link: '/profile',
label: 'Profile',
icon: IconZoomExclamation,
group: 'hidden',
},
{
link: '/staff-avaluation',
label: 'Staff evaluation',
icon: IconChartDots2,
group: 'admin',
},
// { link: '/jira', label: 'Jira', icon: IconSubtask },
// { link: '/custom-theme', label: 'Custom Theme', icon: IconBrush },
// { link: '/general-setting', label: 'General Setting', icon: IconSettings },
// { link: '/packages', label: 'Packages', icon: IconPackages },
// { link: '/discounts', label: 'Discounts', icon: IconDiscount2 },
// { link: '/client', label: 'Clients', icon: IconUsersGroup },
// { link: '/banner', label: 'Banners', icon: IconPanoramaHorizontal },
// { link: '/order', label: 'Orders', icon: IconReceipt2 },
// { link: '/sn-check-history', label: 'Check History', icon: IconListSearch },
// { link: '/contacts', label: 'Contacts', icon: IconAddressBook },
]
type TProps = {
isCompactMenu: boolean
handleSetCompactMenu: () => void
}
const Navbar = ({
isCompactMenu = false,
handleSetCompactMenu = () => {},
}: TProps) => {
const user = useAppSelector((state) => state.authentication.user)
const [active, setActive] = useState<string>('')
//state from dashboard
const location = useLocation()
useEffect(() => {
if (location.pathname) {
const activeMenu = data.filter((i) => i.link === location.pathname)[0]
.label
setActive(activeMenu)
window.history.replaceState({}, document.title)
document.title = activeMenu + ' - Admin - APAC Tech'
}
}, [location?.pathname])
const [opened, setOpened] = useState(false)
const [loading, setLoading] = useState(false)
const [dataChange, setDataChange] = useState({
password: '',
new_password: '',
confirm_password: '',
})
const [countSpam, setCountSpam] = useState(0)
const [avatar, setAvatar] = useState(user.user.avatar)
const navigate = useNavigate()
const dispatch = useAppDispatch()
const passwordRegex =
/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/
const { setColorScheme } = useMantineColorScheme()
const computedColorScheme = useComputedColorScheme('light', {
getInitialValueInEffect: true,
})
useEffect(() => {
setAvatar(user.user.avatar)
}, [user])
// const links = data.map((item) => {
// if (
// user?.user?.permission !== 'admin' &&
// item.link === '/tickets-management'
// )
// return null
// return (
// <a
// className={classes.link}
// data-active={item.label === active || undefined}
// key={item.label}
// onClick={() => {
// // setHeader(item.label);
// setActive(active)
// if (active !== item.label) {
// navigate(item.link)
// }
// }}
// >
// <item.icon className={classes.linkIcon} stroke={1.5} />
// <span
// className={`${classes.itemLabel} ${
// isCompactMenu ? classes.labelCompactMenu : ''
// } `}
// >
// {item.label}
// </span>
// </a>
// )
// })
const group = [
{ name: 'normal', label: 'Normal' },
{ name: 'admin', label: 'Admin' },
]
const links = group.map((g) => {
return (
<div key={g.name}>
<Divider
display={
g.name === 'normal'
? 'block'
: user?.user?.permission.includes(g.name) ||
user?.user?.permission.includes('hr')
? 'block'
: 'none'
}
my="xs"
label={
<span style={{ color: 'white', fontWeight: 700 }}>{g.label}</span>
}
labelPosition="center"
/>
{data
.filter((i) => {
return (
i.group === g.name &&
(user?.user?.permission.includes('admin') ||
user?.user?.permission.includes('hr') ||
g.name !== 'admin')
)
})
.map((item) => (
<a
className={classes.link}
data-active={item.label === active || undefined}
key={item.link}
onClick={() => {
// setHeader(item.label);
setActive(active)
if (active !== item.label) {
navigate(item.link)
}
}}
>
<item.icon className={classes.linkIcon} stroke={1.5} />
<span
className={`${classes.itemLabel} ${
isCompactMenu ? classes.labelCompactMenu : ''
} `}
>
{item.label}
</span>
</a>
))}
</div>
)
})
const handleLogout = useCallback(() => {
dispatch(logout(navigate))
}, [dispatch, navigate])
const handleChangePassword = async () => {
try {
if (countSpam > 5) {
setLoading(true)
notifications.show({
title: 'Error',
message: 'Password error more than 5 times. Logout after 3s',
color: 'red',
})
setTimeout(() => {
localStorage.clear()
window.location.reload()
}, 3000)
} else {
setLoading(true)
const res = await post(
changePassword,
{
email: user.user.email,
password: dataChange.password,
new_password: dataChange.new_password,
confirm_password: dataChange.confirm_password,
},
undefined,
)
if (res.status) {
notifications.show({
title: 'Success',
message: 'Reset password success',
color: 'green',
})
setOpened(false)
setDataChange({
password: '',
new_password: '',
confirm_password: '',
})
handleLogout()
} else {
notifications.show({
title: 'Error',
message: res.errors.password,
color: 'red',
})
}
setCountSpam(countSpam + 1)
setLoading(false)
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
console.log(error)
if (error.response.status === 422) {
const errorMess = error.response.data.message
notifications.show({
title: 'Error',
message: errorMess,
color: 'red',
})
}
setLoading(false)
}
}
const renderQRCode = async () => {
try {
const res = await get(getQRCode + '/' + user?.user?.id)
if (res.status) {
window.open(
import.meta.env.VITE_BACKEND_URL.includes('localhost')
? `${import.meta.env.VITE_BACKEND_URL}storage/qrcode/qrcode_${
user?.user?.id
}.svg`
: `${import.meta.env.VITE_BACKEND_URL}image/storage/qrcode/qrcode_${
user?.user?.id
}.svg`,
'_blank',
)
}
} catch (error) {
console.log(error)
}
}
return (
<>
<nav
className={`${classes.navbar} ${
isCompactMenu ? classes.compactMenu : ''
}`}
>
<div className={classes.navbarMain}>
<Group className={classes.header} justify="space-between">
<Box display={'flex'} w={'100%'}>
<Box style={{ display: 'flex', flexFlow: 'row' }} w={'100%'}>
<Box
w={'90%'}
style={{
display: 'flex',
flexFlow: 'column',
alignItems: 'flex-start',
}}
>
<Avatar
src={
avatar !== ''
? import.meta.env.VITE_BACKEND_URL.includes('local')
? import.meta.env.VITE_BACKEND_URL +
'storage/' +
avatar
: import.meta.env.VITE_BACKEND_URL +
'image/storage/' +
avatar
: ''
}
alt="User Avatar"
size={60}
radius="xl"
mb={5}
onClick={() => navigate('/profile')}
style={{
cursor: 'pointer',
}}
/>
<Code
fw={700}
className={classes.version}
onClick={() => navigate('/profile')}
style={{
cursor: 'pointer',
}}
>
{user.user.name}
</Code>
</Box>
<Box
w={'10%'}
style={{
display: 'flex',
justifyContent: 'center',
}}
>
<Tooltip label="Your QR code" fz={'xs'}>
<IconQrcode
onClick={() => renderQRCode()}
color="#fff164"
width={28}
height={28}
style={{
border: 'solid 2px orange',
borderRadius: '5px',
cursor: 'pointer',
}}
/>
</Tooltip>
</Box>
{/* <Box
w={'100%'}
style={{
display: 'flex',
flexFlow: 'row',
justifyContent: 'space-between', // Aligns username to the left and QR code to the right
alignItems: 'center',
}}
>
<Code fw={700} className={classes.version}>
{user.user.name}
</Code>
<Tooltip label="Your QR code" fz={'xs'}>
<IconQrcode
onClick={() => renderQRCode()}
color="#fff164"
width={28}
height={28}
style={{
border: 'solid 2px orange',
borderRadius: '5px',
cursor: 'pointer',
}}
/>
</Tooltip>
</Box> */}
</Box>
</Box>
</Group>
{links}
</div>
<div className={classes.footer}>
<a
href="#"
className={classes.link}
onClick={() =>
setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')
}
>
{computedColorScheme === 'light' ? (
<IconSun className={classes.linkIcon} stroke={1.5} />
) : (
<IconMoon className={classes.linkIcon} stroke={1.5} />
)}
<span
className={`${classes.itemLabel} ${
isCompactMenu ? classes.labelCompactMenu : ''
}`}
>
Change mode
</span>
</a>
{/* <a href="#" className={classes.link} onClick={() => setOpened(true)}>
<IconPasswordUser className={classes.linkIcon} stroke={1.5} />
<span
className={`${classes.itemLabel} ${
isCompactMenu ? classes.labelCompactMenu : ''
}`}
>
Change password
</span>
</a> */}
<a href="#" className={classes.link} onClick={handleLogout}>
<IconLogout className={classes.linkIcon} stroke={1.5} />
<span
className={`${classes.itemLabel} ${
isCompactMenu ? classes.labelCompactMenu : ''
}`}
>
Logout
</span>
</a>
<div className={classes.footer}></div>
<a
href="#"
className={classes.link}
onClick={handleSetCompactMenu}
style={{ margin: '0 20px', padding: '0 0 10px 0' }}
>
{isCompactMenu ? (
<IconLayoutSidebarLeftExpand
className={classes.linkIcon}
stroke={1.5}
/>
) : (
<IconLayoutSidebarRightExpand
className={classes.linkIcon}
stroke={1.5}
/>
)}
<span
className={`${classes.itemLabel} ${
isCompactMenu ? classes.labelCompactMenu : ''
}`}
>
Collapse Sidebar
</span>
</a>
</div>
</nav>
<Modal
opened={opened}
onClose={() => {
setOpened(false)
setDataChange({
password: '',
new_password: '',
confirm_password: '',
})
}}
title={
<Text
fw={700}
fz={'1.2rem'}
className={`${classes.itemLabel} ${
isCompactMenu ? classes.labelCompactMenu : ''
}`}
>
Change password
</Text>
}
>
<Box p="sm">
<TextInput
label="E-mail"
value={user.user.email}
disabled
mb={'md'}
></TextInput>
<PasswordInput
label="Current password"
required
placeholder="Current password"
maxLength={32}
value={dataChange.password}
onChange={(e) => {
setDataChange({ ...dataChange, password: e.target.value })
}}
error={
dataChange.password.length < 8 &&
dataChange.password !== '' &&
'Length 8 characters or more'
}
/>
<PasswordRequirementInput
requirements={requirementsPassword}
value={dataChange}
setValue={setDataChange}
label="New password"
placeholder="New password"
name="new_password"
mb="md"
/>
<PasswordInput
label="Confirm password"
required
placeholder="Confirm password"
maxLength={32}
value={dataChange.confirm_password}
onChange={(e) => {
setDataChange({
...dataChange,
confirm_password: e.target.value,
})
}}
error={
dataChange.new_password !== dataChange.confirm_password &&
dataChange.confirm_password !== '' &&
'Password do not match'
}
></PasswordInput>
<Button
style={{ float: 'right' }}
mb={'lg'}
mt={'lg'}
loading={loading}
disabled={
loading === false &&
dataChange.password !== '' &&
passwordRegex.test(dataChange.new_password) &&
dataChange.new_password === dataChange.confirm_password &&
dataChange.new_password !== ''
? false
: true
}
onClick={() => handleChangePassword()}
>
Update
</Button>
</Box>
</Modal>
</>
)
}
export default Navbar