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 { useAppDispatch, useAppSelector } from '@/rtk/hooks'
 | 
			
		||||
import {
 | 
			
		||||
  Avatar,
 | 
			
		||||
  Box,
 | 
			
		||||
  Button,
 | 
			
		||||
  Code,
 | 
			
		||||
| 
						 | 
				
			
			@ -94,6 +95,12 @@ const data = [
 | 
			
		|||
    icon: IconZoomExclamation,
 | 
			
		||||
    group: 'admin',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    link: '/profile',
 | 
			
		||||
    label: 'Profile',
 | 
			
		||||
    icon: IconZoomExclamation,
 | 
			
		||||
    group: 'hidden',
 | 
			
		||||
  },
 | 
			
		||||
  // { link: '/jira', label: 'Jira', icon: IconSubtask },
 | 
			
		||||
  // { link: '/custom-theme', label: 'Custom Theme', icon: IconBrush },
 | 
			
		||||
  // { link: '/general-setting', label: 'General Setting', icon: IconSettings },
 | 
			
		||||
| 
						 | 
				
			
			@ -338,22 +345,87 @@ const Navbar = ({
 | 
			
		|||
      >
 | 
			
		||||
        <div className={classes.navbarMain}>
 | 
			
		||||
          <Group className={classes.header} justify="space-between">
 | 
			
		||||
            <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 display={'flex'} w={'100%'}>
 | 
			
		||||
              <Box style={{ display: 'flex', flexFlow: 'row' }} w={'100%'}>
 | 
			
		||||
                <Box
 | 
			
		||||
                  w={'90%'}
 | 
			
		||||
                  style={{
 | 
			
		||||
                    display: 'flex',
 | 
			
		||||
                    flexFlow: 'column',
 | 
			
		||||
                    alignItems: 'center',
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  <Avatar
 | 
			
		||||
                    src={user.user.avatarUrl || '/public/backgroundLogin.png'}
 | 
			
		||||
                    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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
  Box,
 | 
			
		||||
  Button,
 | 
			
		||||
  Select,
 | 
			
		||||
  Table,
 | 
			
		||||
  Text,
 | 
			
		||||
  Title
 | 
			
		||||
} from '@mantine/core'
 | 
			
		||||
import { Box, Button, Select, Table, Text, Title } from '@mantine/core'
 | 
			
		||||
import { notifications } from '@mantine/notifications'
 | 
			
		||||
import { useEffect, useState } from 'react'
 | 
			
		||||
import classes from './SprintReview.module.css'
 | 
			
		||||
| 
						 | 
				
			
			@ -271,16 +264,48 @@ const SprintReview = () => {
 | 
			
		|||
                </Table.Td>
 | 
			
		||||
              </Table.Tr>
 | 
			
		||||
            ))}
 | 
			
		||||
            <Table.Tr bg="#d4eac7" style={rowStyle}>
 | 
			
		||||
            <Table.Tr style={rowStyle}>
 | 
			
		||||
              <Table.Td
 | 
			
		||||
                colSpan={1}
 | 
			
		||||
                style={{ textAlign: 'center' }}
 | 
			
		||||
                bg="#d4eac7"
 | 
			
		||||
              >
 | 
			
		||||
                Final result
 | 
			
		||||
                <Text fw={600} color='red'> Final result </Text>
 | 
			
		||||
              </Table.Td>
 | 
			
		||||
              <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 colSpan={3}></Table.Td>
 | 
			
		||||
            </Table.Tr>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ import ProtectedRoute from '@/components/ProtectedRoute/ProtectedRoute'
 | 
			
		|||
import PageLogin from '@/pages/Auth/Login/Login'
 | 
			
		||||
import LeaveManagement from '@/pages/LeaveManagement/LeaveManagement'
 | 
			
		||||
import PageNotFound from '@/pages/NotFound/NotFound'
 | 
			
		||||
import Profile from '@/pages/Profile/Profile'
 | 
			
		||||
import SprintReview from '@/pages/SprintReview/SprintReview'
 | 
			
		||||
import TestReport from '@/pages/TestReport/TestReport'
 | 
			
		||||
import Tickets from '@/pages/Tickets/Tickets'
 | 
			
		||||
| 
						 | 
				
			
			@ -174,6 +175,20 @@ const mainRoutes = [
 | 
			
		|||
      </ProtectedRoute>
 | 
			
		||||
    ),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: '/profile',
 | 
			
		||||
    element: (
 | 
			
		||||
      <ProtectedRoute mode="route" permission="admin,hr">
 | 
			
		||||
        <BasePage
 | 
			
		||||
          main={
 | 
			
		||||
            <>
 | 
			
		||||
              <Profile />
 | 
			
		||||
            </>
 | 
			
		||||
          }
 | 
			
		||||
        ></BasePage>
 | 
			
		||||
      </ProtectedRoute>
 | 
			
		||||
    ),
 | 
			
		||||
  },
 | 
			
		||||
  // {
 | 
			
		||||
  //   path: '/packages',
 | 
			
		||||
  //   element: (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue