Merge pull request 'Sprint-3/MS-28' (#24) from Sprint-3/MS-28 into master
Reviewed-on: #24
This commit is contained in:
commit
a823b6812f
|
|
@ -5,6 +5,7 @@ import { get, post } from '@/rtk/helpers/apiService'
|
||||||
import { requirementsPassword } from '@/rtk/helpers/variables'
|
import { requirementsPassword } from '@/rtk/helpers/variables'
|
||||||
import { useAppDispatch, useAppSelector } from '@/rtk/hooks'
|
import { useAppDispatch, useAppSelector } from '@/rtk/hooks'
|
||||||
import {
|
import {
|
||||||
|
Avatar,
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Code,
|
Code,
|
||||||
|
|
@ -94,6 +95,12 @@ const data = [
|
||||||
icon: IconZoomExclamation,
|
icon: IconZoomExclamation,
|
||||||
group: 'admin',
|
group: 'admin',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
link: '/profile',
|
||||||
|
label: 'Profile',
|
||||||
|
icon: IconZoomExclamation,
|
||||||
|
group: 'hidden',
|
||||||
|
},
|
||||||
// { link: '/jira', label: 'Jira', icon: IconSubtask },
|
// { link: '/jira', label: 'Jira', icon: IconSubtask },
|
||||||
// { link: '/custom-theme', label: 'Custom Theme', icon: IconBrush },
|
// { link: '/custom-theme', label: 'Custom Theme', icon: IconBrush },
|
||||||
// { link: '/general-setting', label: 'General Setting', icon: IconSettings },
|
// { link: '/general-setting', label: 'General Setting', icon: IconSettings },
|
||||||
|
|
@ -338,22 +345,87 @@ const Navbar = ({
|
||||||
>
|
>
|
||||||
<div className={classes.navbarMain}>
|
<div className={classes.navbarMain}>
|
||||||
<Group className={classes.header} justify="space-between">
|
<Group className={classes.header} justify="space-between">
|
||||||
<Code fw={700} className={classes.version}>
|
<Box display={'flex'} w={'100%'}>
|
||||||
{user.user.name}
|
<Box style={{ display: 'flex', flexFlow: 'row' }} w={'100%'}>
|
||||||
</Code>
|
<Box
|
||||||
<Tooltip label="Your QR code" fz={'xs'}>
|
w={'90%'}
|
||||||
<IconQrcode
|
style={{
|
||||||
onClick={() => renderQRCode()}
|
display: 'flex',
|
||||||
color="#fff164"
|
flexFlow: 'column',
|
||||||
width={28}
|
alignItems: 'center',
|
||||||
height={28}
|
}}
|
||||||
style={{
|
>
|
||||||
border: 'solid 2px orange',
|
<Avatar
|
||||||
borderRadius: '5px',
|
src={user.user.avatarUrl || '/public/backgroundLogin.png'}
|
||||||
cursor: 'pointer',
|
alt="User Avatar"
|
||||||
}}
|
size={60}
|
||||||
/>
|
radius="xl"
|
||||||
</Tooltip>
|
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>
|
</Group>
|
||||||
{links}
|
{links}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,196 @@
|
||||||
|
:root {
|
||||||
|
--primary-color: rgb(9, 132, 132);
|
||||||
|
--secondary-color: #ff9100;
|
||||||
|
--hover-background: rgba(203, 203, 203, 0.809);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
background-color: light-dark(var(white), var(--mantine-color-dark-7));
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 var(--mantine-spacing-sm) var(--mantine-spacing-lg)
|
||||||
|
var(--mantine-spacing-sm);
|
||||||
|
border-bottom: solid rgba(201, 201, 201, 0.377) 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optionIcon {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleteIcon,
|
||||||
|
.editIcon {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleteIcon {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editIcon {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editIcon:hover,
|
||||||
|
.deleteIcon:hover {
|
||||||
|
background-color: var(--hover-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
background-color: light-dark(white, #2d353c);
|
||||||
|
text-align: center;
|
||||||
|
border: solid 1px var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialogText {
|
||||||
|
color: light-dark(#2d353c, white);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wrapper for the entire user info and project section */
|
||||||
|
.userInfoSection {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 20px;
|
||||||
|
gap: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar section for user information */
|
||||||
|
.sidebar {
|
||||||
|
width: 30%;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main section for projects and sprints */
|
||||||
|
.projectInvolvement {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Styles */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.userInfoSection {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar,
|
||||||
|
.projectInvolvement {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sprintContainer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconWrapper {
|
||||||
|
margin-right: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projectHeader {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-top: 10px;
|
||||||
|
/* background-color: var(--hover-background); */
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projectHeader:hover {
|
||||||
|
background-color: var(--hover-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sprintList {
|
||||||
|
padding-left: 20px; /* Indentation for sprints under the project */
|
||||||
|
}
|
||||||
|
|
||||||
|
.sprintHeader2 {
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
width: 100%;
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
margin-left: 20px;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sprintHeader {
|
||||||
|
margin-top: 5px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
margin-left: 20px;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sprintHeader2:hover {
|
||||||
|
background-color: var(--hover-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.criteriaTable {
|
||||||
|
padding-left: 55px; /* Further indentation for the criteria table */
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: var(--mantine-font-size-sm);
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
padding: var(--mantine-spacing-xs) var(--mantine-spacing-sm);
|
||||||
|
margin: var(--mantine-spacing-xs);
|
||||||
|
border-radius: var(--mantine-radius-sm);
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #00a164;
|
||||||
|
|
||||||
|
@mixin hover {
|
||||||
|
/* background-color: #035737; */
|
||||||
|
color: var(--mantine-color-white);
|
||||||
|
|
||||||
|
.linkIcon {
|
||||||
|
color: var(--mantine-color-white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-active] {
|
||||||
|
&,
|
||||||
|
&:hover {
|
||||||
|
box-shadow: var(--mantine-shadow-sm);
|
||||||
|
background-color: #6089afb7;
|
||||||
|
color: var(--mantine-color-white);
|
||||||
|
|
||||||
|
.linkIcon {
|
||||||
|
color: var(--mantine-color-white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkIcon {
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
width: rem(20px);
|
||||||
|
height: rem(22px);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,484 @@
|
||||||
|
import { changePassword } from '@/api/Auth'
|
||||||
|
import PasswordRequirementInput from '@/components/PasswordRequirementInput/PasswordRequirementInput'
|
||||||
|
import { logout } from '@/rtk/dispatches/auth'
|
||||||
|
import { post } from '@/rtk/helpers/apiService'
|
||||||
|
import { requirementsPassword } from '@/rtk/helpers/variables'
|
||||||
|
import { useAppDispatch, useAppSelector } from '@/rtk/hooks'
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
PasswordInput,
|
||||||
|
Table,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
Title
|
||||||
|
} from '@mantine/core'
|
||||||
|
import { notifications } from '@mantine/notifications'
|
||||||
|
import { IconCornerDownRight, IconPasswordUser } from '@tabler/icons-react'
|
||||||
|
import { useCallback, useState } from 'react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import classes from './Profile.module.css'
|
||||||
|
interface TableRow {
|
||||||
|
// Add properties for each column in the table
|
||||||
|
criteria: string
|
||||||
|
note: string
|
||||||
|
createdBy: string
|
||||||
|
point: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExpandedProjects {
|
||||||
|
[projectName: string]: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExpandedSprints {
|
||||||
|
[projectName: string]: {
|
||||||
|
[sprintName: string]: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CriteriaTable = ({ data }: { data: TableRow[] }) => (
|
||||||
|
<Table striped highlightOnHover withTableBorder withColumnBorders>
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr bg="#228be66b">
|
||||||
|
<Table.Th style={{ textAlign: 'center' }}>Criteria</Table.Th>
|
||||||
|
<Table.Th style={{ textAlign: 'center' }}>Note</Table.Th>
|
||||||
|
<Table.Th style={{ textAlign: 'center' }}>Created by</Table.Th>
|
||||||
|
<Table.Th style={{ textAlign: 'center' }}>Point</Table.Th>
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
{data.map((row, index) => (
|
||||||
|
<Table.Tr key={index}>
|
||||||
|
<Table.Td>{row.criteria}</Table.Td>
|
||||||
|
<Table.Td>{row.note}</Table.Td>
|
||||||
|
<Table.Td>{row.createdBy}</Table.Td>
|
||||||
|
<Table.Td>{row.point}</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
))}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
)
|
||||||
|
|
||||||
|
const projectsData = [
|
||||||
|
{
|
||||||
|
name: 'Project Name 1',
|
||||||
|
sprints: [
|
||||||
|
{
|
||||||
|
name: 'Sprint 1',
|
||||||
|
criterias: [
|
||||||
|
{
|
||||||
|
criteria: 'Criteria 1',
|
||||||
|
note: 'String (default when selecting point)',
|
||||||
|
createdBy: 'User 1',
|
||||||
|
point: '1-5',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
criteria: 'Criteria 2',
|
||||||
|
note: 'String (default when selecting point)',
|
||||||
|
createdBy: 'User 2',
|
||||||
|
point: '1-5',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Sprint 2',
|
||||||
|
criterias: [
|
||||||
|
{
|
||||||
|
criteria: 'Criteria 3',
|
||||||
|
note: 'String (default when selecting point)',
|
||||||
|
createdBy: 'User 3',
|
||||||
|
point: '1-5',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
criteria: 'Criteria 4',
|
||||||
|
note: 'String (default when selecting point)',
|
||||||
|
createdBy: 'User 4',
|
||||||
|
point: '1-5',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Project Name 2',
|
||||||
|
sprints: [
|
||||||
|
{
|
||||||
|
name: 'Sprint 1',
|
||||||
|
criterias: [
|
||||||
|
{
|
||||||
|
criteria: 'Criteria 7',
|
||||||
|
note: 'String (default when selecting point)',
|
||||||
|
createdBy: 'User 7',
|
||||||
|
point: '1-5',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const isCompactMenu = false
|
||||||
|
const Profile = () => {
|
||||||
|
const user = useAppSelector((state) => state.authentication.user)
|
||||||
|
const [opened, setOpened] = useState(false)
|
||||||
|
const [dataChange, setDataChange] = useState({
|
||||||
|
password: '',
|
||||||
|
new_password: '',
|
||||||
|
confirm_password: '',
|
||||||
|
})
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
// const [projects, setProjects] = useState(projectsData)
|
||||||
|
const [expandedProjects, setExpandedProjects] = useState<ExpandedProjects>({})
|
||||||
|
const [expandedSprints, setExpandedSprints] = useState<ExpandedSprints>({})
|
||||||
|
|
||||||
|
const [countSpam, setCountSpam] = useState(0)
|
||||||
|
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,
|
||||||
|
// })
|
||||||
|
|
||||||
|
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 handleLogout = useCallback(() => {
|
||||||
|
dispatch(logout(navigate))
|
||||||
|
}, [dispatch, navigate])
|
||||||
|
const handleProjectToggle = (projectName: string) => {
|
||||||
|
setExpandedProjects((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[projectName]: !prev[projectName], // Toggle state for this project
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSprintToggle = (projectName: string, sprintName: string) => {
|
||||||
|
setExpandedSprints((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[projectName]: {
|
||||||
|
...prev[projectName],
|
||||||
|
[sprintName]: !prev[projectName]?.[sprintName], // Toggle state for this sprint
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={classes.title}>
|
||||||
|
<h3>
|
||||||
|
<Text>Admin/</Text>
|
||||||
|
Profile
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Box className={classes.userInfoSection} display="flex">
|
||||||
|
<Box className={classes.sidebar}>
|
||||||
|
<Title order={3}>User Information</Title>
|
||||||
|
<Box
|
||||||
|
w={'100%'}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexFlow: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
src={null || '/public/backgroundLogin.png'}
|
||||||
|
alt="User Avatar"
|
||||||
|
size={150}
|
||||||
|
// radius="xl"
|
||||||
|
mb={5}
|
||||||
|
mt={5}
|
||||||
|
onClick={() => {
|
||||||
|
console.log('update avatar')
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
// border: '2px solid black',
|
||||||
|
borderRadius: '50%',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box className={classes.userInfo} mt="md">
|
||||||
|
<TextInput
|
||||||
|
label="Name"
|
||||||
|
placeholder="Name"
|
||||||
|
mb="md"
|
||||||
|
readOnly
|
||||||
|
style={{ pointerEvents: 'none' }}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Email"
|
||||||
|
type="email"
|
||||||
|
placeholder="Email"
|
||||||
|
mb="md"
|
||||||
|
readOnly
|
||||||
|
style={{ pointerEvents: 'none' }}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Join Date"
|
||||||
|
type="date"
|
||||||
|
mb="md"
|
||||||
|
readOnly
|
||||||
|
style={{ pointerEvents: 'none' }}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Permission(s)"
|
||||||
|
placeholder="Permissions"
|
||||||
|
readOnly
|
||||||
|
style={{ pointerEvents: 'none' }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
w={'100%'}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexFlow: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
mt={10}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className={classes.link}
|
||||||
|
onClick={() => setOpened(true)}
|
||||||
|
>
|
||||||
|
<IconPasswordUser className={classes.linkIcon} stroke={1.5} />
|
||||||
|
<span
|
||||||
|
className={`${classes.itemLabel} ${classes.labelCompactMenu}`}
|
||||||
|
>
|
||||||
|
Change password
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box className={classes.projectInvolvement}>
|
||||||
|
<Title order={3}>Project Involved</Title>
|
||||||
|
<Box className={classes.project} mt="lg">
|
||||||
|
{projectsData.map((project) => (
|
||||||
|
<div key={project.name}>
|
||||||
|
{/* Project Header */}
|
||||||
|
<Box
|
||||||
|
className={classes.projectHeader}
|
||||||
|
onClick={() => handleProjectToggle(project.name)}
|
||||||
|
>
|
||||||
|
<Text ml="sm" fw={600}>
|
||||||
|
{project.name}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Project's Sprints */}
|
||||||
|
{expandedProjects[project.name] && (
|
||||||
|
<Box className={classes.sprintList}>
|
||||||
|
{project.sprints.map((sprint) => (
|
||||||
|
<div key={sprint.name}>
|
||||||
|
<Box
|
||||||
|
className={classes.sprintHeader}
|
||||||
|
onClick={() =>
|
||||||
|
handleSprintToggle(project.name, sprint.name)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconCornerDownRight size={20} />
|
||||||
|
<Text
|
||||||
|
ml="xs"
|
||||||
|
className={classes.sprintHeader2}
|
||||||
|
fw={600}
|
||||||
|
>
|
||||||
|
{sprint.name}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Criteria Table */}
|
||||||
|
{expandedSprints[project.name]?.[sprint.name] && (
|
||||||
|
<Box className={classes.criteriaTable}>
|
||||||
|
{/* <CriteriaTable data={sprint.criteria} /> */}
|
||||||
|
<CriteriaTable
|
||||||
|
data={sprint.criterias.map((criteria) => ({
|
||||||
|
...criteria,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Profile
|
||||||
|
|
@ -1,11 +1,4 @@
|
||||||
import {
|
import { Box, Button, Select, Table, Text, Title } from '@mantine/core'
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Select,
|
|
||||||
Table,
|
|
||||||
Text,
|
|
||||||
Title
|
|
||||||
} from '@mantine/core'
|
|
||||||
import { notifications } from '@mantine/notifications'
|
import { notifications } from '@mantine/notifications'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import classes from './SprintReview.module.css'
|
import classes from './SprintReview.module.css'
|
||||||
|
|
@ -271,16 +264,48 @@ const SprintReview = () => {
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
))}
|
))}
|
||||||
<Table.Tr bg="#d4eac7" style={rowStyle}>
|
<Table.Tr style={rowStyle}>
|
||||||
<Table.Td
|
<Table.Td
|
||||||
colSpan={1}
|
colSpan={1}
|
||||||
style={{ textAlign: 'center' }}
|
style={{ textAlign: 'center' }}
|
||||||
bg="#d4eac7"
|
bg="#d4eac7"
|
||||||
>
|
>
|
||||||
Final result
|
<Text fw={600} color='red'> Final result </Text>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td bg="#d4eac7">
|
<Table.Td bg="#d4eac7">
|
||||||
<Select data={pointsOptions} defaultValue="3" size="xs" />
|
<Select
|
||||||
|
data={pointsOptions}
|
||||||
|
defaultValue="3"
|
||||||
|
size="xs"
|
||||||
|
// style={{
|
||||||
|
// color: 'red',
|
||||||
|
// fontWeight: '700',
|
||||||
|
// fontSize: 'initial',
|
||||||
|
// }}
|
||||||
|
styles={() => ({
|
||||||
|
input: {
|
||||||
|
// borderColor: theme.colors.blue[6], // Border color for the input
|
||||||
|
// '&:hover': {
|
||||||
|
// borderColor: theme.colors.blue[4], // Border color on hover
|
||||||
|
// },
|
||||||
|
color: 'red',
|
||||||
|
fontWeight: '600',
|
||||||
|
fontSize: 'initial',
|
||||||
|
},
|
||||||
|
// dropdown: {
|
||||||
|
// backgroundColor: theme.colors.gray[0], // Background color for the dropdown
|
||||||
|
// },
|
||||||
|
// item: {
|
||||||
|
// '&[data-selected]': {
|
||||||
|
// backgroundColor: theme.colors.blue[6], // Background color for the selected item
|
||||||
|
// color: theme.white, // Text color for the selected item
|
||||||
|
// },
|
||||||
|
// '&[data-hovered]': {
|
||||||
|
// backgroundColor: theme.colors.blue[6], // Background color for hovered items
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
})}
|
||||||
|
/>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td colSpan={3}></Table.Td>
|
<Table.Td colSpan={3}></Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import ProtectedRoute from '@/components/ProtectedRoute/ProtectedRoute'
|
||||||
import PageLogin from '@/pages/Auth/Login/Login'
|
import PageLogin from '@/pages/Auth/Login/Login'
|
||||||
import LeaveManagement from '@/pages/LeaveManagement/LeaveManagement'
|
import LeaveManagement from '@/pages/LeaveManagement/LeaveManagement'
|
||||||
import PageNotFound from '@/pages/NotFound/NotFound'
|
import PageNotFound from '@/pages/NotFound/NotFound'
|
||||||
|
import Profile from '@/pages/Profile/Profile'
|
||||||
import SprintReview from '@/pages/SprintReview/SprintReview'
|
import SprintReview from '@/pages/SprintReview/SprintReview'
|
||||||
import TestReport from '@/pages/TestReport/TestReport'
|
import TestReport from '@/pages/TestReport/TestReport'
|
||||||
import Tickets from '@/pages/Tickets/Tickets'
|
import Tickets from '@/pages/Tickets/Tickets'
|
||||||
|
|
@ -174,6 +175,20 @@ const mainRoutes = [
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/profile',
|
||||||
|
element: (
|
||||||
|
<ProtectedRoute mode="route" permission="admin,hr">
|
||||||
|
<BasePage
|
||||||
|
main={
|
||||||
|
<>
|
||||||
|
<Profile />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
></BasePage>
|
||||||
|
</ProtectedRoute>
|
||||||
|
),
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// path: '/packages',
|
// path: '/packages',
|
||||||
// element: (
|
// element: (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue