Merge branch 'master' of https://gitea.nswteam.net/joseph/ManagementSystem into Sprint-4/MS-38-BE-Evaluation
This commit is contained in:
commit
62df9728c7
|
|
@ -83,6 +83,7 @@ export const DataTableAll = ({
|
||||||
searchInput,
|
searchInput,
|
||||||
checkBox,
|
checkBox,
|
||||||
size,
|
size,
|
||||||
|
infoTotal,
|
||||||
}: {
|
}: {
|
||||||
data: any[]
|
data: any[]
|
||||||
columns: Column[]
|
columns: Column[]
|
||||||
|
|
@ -90,6 +91,7 @@ export const DataTableAll = ({
|
||||||
searchInput?: boolean
|
searchInput?: boolean
|
||||||
checkBox?: boolean
|
checkBox?: boolean
|
||||||
size: string
|
size: string
|
||||||
|
infoTotal?: React.ReactNode // Set the type to ReactNode to allow JSX elements
|
||||||
}) => {
|
}) => {
|
||||||
const [Tdata, setTData] = useState<any[]>(data)
|
const [Tdata, setTData] = useState<any[]>(data)
|
||||||
// const [tempData, setTempData] = useState<any[]>([])
|
// const [tempData, setTempData] = useState<any[]>([])
|
||||||
|
|
@ -323,7 +325,14 @@ export const DataTableAll = ({
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box
|
||||||
|
className={classes.paginationBox}
|
||||||
|
display={infoTotal ? 'flex' : 'none'}
|
||||||
|
>
|
||||||
|
<Text fz={'sm'} ta={'right'}>
|
||||||
|
{infoTotal}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
className={classes.paginationBox}
|
className={classes.paginationBox}
|
||||||
display={pagination ? 'flex' : 'none'}
|
display={pagination ? 'flex' : 'none'}
|
||||||
|
|
@ -573,7 +582,7 @@ export const DataTablePagination = ({
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const removeParam = (name:string) => {
|
const removeParam = (name: string) => {
|
||||||
// Create a URL object
|
// Create a URL object
|
||||||
let url = new URL(window.location.href)
|
let url = new URL(window.location.href)
|
||||||
|
|
||||||
|
|
@ -682,7 +691,8 @@ export const DataTablePagination = ({
|
||||||
search: urlParams.toString(),
|
search: urlParams.toString(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
statusSort.status === 'clear' && removeParam(`order_by_${statusSort.name}`)
|
statusSort.status === 'clear' &&
|
||||||
|
removeParam(`order_by_${statusSort.name}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(error)
|
console.warn(error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import {
|
||||||
IconBinaryTree2,
|
IconBinaryTree2,
|
||||||
IconCalendar,
|
IconCalendar,
|
||||||
IconCalendarClock,
|
IconCalendarClock,
|
||||||
|
IconChartDots2,
|
||||||
IconDevices,
|
IconDevices,
|
||||||
IconLayoutSidebarLeftExpand,
|
IconLayoutSidebarLeftExpand,
|
||||||
IconLayoutSidebarRightExpand,
|
IconLayoutSidebarRightExpand,
|
||||||
|
|
@ -37,7 +38,7 @@ import {
|
||||||
IconSun,
|
IconSun,
|
||||||
IconTicket,
|
IconTicket,
|
||||||
IconUsersGroup,
|
IconUsersGroup,
|
||||||
IconZoomExclamation,
|
IconZoomExclamation
|
||||||
} from '@tabler/icons-react'
|
} from '@tabler/icons-react'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
|
|
@ -107,6 +108,12 @@ const data = [
|
||||||
icon: IconZoomExclamation,
|
icon: IconZoomExclamation,
|
||||||
group: 'hidden',
|
group: 'hidden',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
link: '/staff-avaluation',
|
||||||
|
label: 'Staff evaluation',
|
||||||
|
icon: IconChartDots2,
|
||||||
|
group: 'admin',
|
||||||
|
},
|
||||||
// { 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 },
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
.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: #457bad;
|
||||||
|
color: white;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projectHeader:hover {
|
||||||
|
background-color: var(--hover-background);
|
||||||
|
color: #2d353c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
background-color: rgba(162, 162, 162, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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: 35px; /* Further indentation for the criteria table */
|
||||||
|
}
|
||||||
|
|
||||||
|
.project {
|
||||||
|
margin-top: 1rem !important;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
import { Box, Table, Text } from '@mantine/core'
|
||||||
|
import { IconCornerDownRight } from '@tabler/icons-react'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import classes from './ProjectInvolvement.module.css'
|
||||||
|
|
||||||
|
interface Project {
|
||||||
|
name: string
|
||||||
|
sprints: Sprint[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Sprint {
|
||||||
|
name: string
|
||||||
|
criterias: TableRow[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TableRow {
|
||||||
|
criteria: string
|
||||||
|
note: string
|
||||||
|
createdBy: string
|
||||||
|
point: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExpandedProjects {
|
||||||
|
[projectName: string]: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExpandedSprints {
|
||||||
|
[projectName: string]: {
|
||||||
|
[sprintName: string]: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProjectInvolvementProps {
|
||||||
|
dataProfile: Project[]
|
||||||
|
page: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CriteriaTableProps = {
|
||||||
|
data: TableRow[]
|
||||||
|
page: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const CriteriaTable: React.FC<CriteriaTableProps> = ({ data, page }) => (
|
||||||
|
<Table striped highlightOnHover withTableBorder withColumnBorders>
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr bg="#228be66b">
|
||||||
|
<Table.Th style={{ textAlign: 'center', width: '25%' }}>
|
||||||
|
Criteria
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th style={{ textAlign: 'center', width: '45%' }}>Note</Table.Th>
|
||||||
|
<Table.Th style={{ textAlign: 'center', width: '20%' }}>
|
||||||
|
Created by
|
||||||
|
</Table.Th>
|
||||||
|
{page == 'profile' ? (
|
||||||
|
''
|
||||||
|
) : (
|
||||||
|
<Table.Th style={{ textAlign: 'center', width: '10%' }}>
|
||||||
|
Point
|
||||||
|
</Table.Th>
|
||||||
|
)}
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
{data.map((row, index) => (
|
||||||
|
<Table.Tr key={index}>
|
||||||
|
<Table.Td style={{ textAlign: 'start' }}>{row.criteria}</Table.Td>
|
||||||
|
<Table.Td style={{ textAlign: 'start' }}>{row.note}</Table.Td>
|
||||||
|
<Table.Td style={{ textAlign: 'start' }}>{row.createdBy}</Table.Td>
|
||||||
|
{page == 'profile' ? (
|
||||||
|
''
|
||||||
|
) : (
|
||||||
|
<Table.Td>{row.point == 0 ? '' : row.point}</Table.Td>
|
||||||
|
)}
|
||||||
|
</Table.Tr>
|
||||||
|
))}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
)
|
||||||
|
|
||||||
|
const ProjectInvolvement = ({ dataProfile, page }: ProjectInvolvementProps) => {
|
||||||
|
const [expandedProjects, setExpandedProjects] = useState<ExpandedProjects>({})
|
||||||
|
const [expandedSprints, setExpandedSprints] = useState<ExpandedSprints>({})
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Box className={classes.project} mt="lg">
|
||||||
|
{dataProfile.map((project) => (
|
||||||
|
<div key={project.name}>
|
||||||
|
<Box
|
||||||
|
className={classes.projectHeader}
|
||||||
|
onClick={() => handleProjectToggle(project.name)}
|
||||||
|
>
|
||||||
|
<Text ml="sm" fw={600}>
|
||||||
|
{project.name}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{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>
|
||||||
|
|
||||||
|
{expandedSprints[project.name]?.[sprint.name] && (
|
||||||
|
<Box className={classes.criteriaTable}>
|
||||||
|
<CriteriaTable data={sprint.criterias} page={page} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectInvolvement
|
||||||
|
|
@ -1,73 +1,28 @@
|
||||||
import { getProfilesData, updateProfilesData } from '@/api/Admin'
|
import { getProfilesData, updateProfilesData } from '@/api/Admin'
|
||||||
import { changePassword } from '@/api/Auth'
|
import { changePassword } from '@/api/Auth'
|
||||||
import PasswordRequirementInput from '@/components/PasswordRequirementInput/PasswordRequirementInput'
|
import PasswordRequirementInput from '@/components/PasswordRequirementInput/PasswordRequirementInput'
|
||||||
|
import ProjectInvolvement from '@/components/ProjectInvolvement/ProjectInvolvement'
|
||||||
import { logout } from '@/rtk/dispatches/auth'
|
import { logout } from '@/rtk/dispatches/auth'
|
||||||
import { get, post, postImage } from '@/rtk/helpers/apiService'
|
import { get, post, postImage } 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 { getUser } from '@/rtk/localStorage'
|
import { getUser } from '@/rtk/localStorage'
|
||||||
|
import { success } from '@/rtk/slices/auth'
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Modal,
|
Modal,
|
||||||
PasswordInput,
|
PasswordInput,
|
||||||
Table,
|
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
Title,
|
Title,
|
||||||
} from '@mantine/core'
|
} from '@mantine/core'
|
||||||
import { notifications } from '@mantine/notifications'
|
import { notifications } from '@mantine/notifications'
|
||||||
import { IconCornerDownRight, IconPasswordUser } from '@tabler/icons-react'
|
import { IconPasswordUser } from '@tabler/icons-react'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import classes from './Profile.module.css'
|
import classes from './Profile.module.css'
|
||||||
import { success } from '@/rtk/slices/auth'
|
|
||||||
|
|
||||||
interface TableRow {
|
|
||||||
// Add properties for each column in the table
|
|
||||||
criteria: string
|
|
||||||
note: string
|
|
||||||
createdBy: string
|
|
||||||
point: number
|
|
||||||
}
|
|
||||||
|
|
||||||
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', width: '25%' }}>
|
|
||||||
Criteria
|
|
||||||
</Table.Th>
|
|
||||||
<Table.Th style={{ textAlign: 'center', width: '45%' }}>Note</Table.Th>
|
|
||||||
<Table.Th style={{ textAlign: 'center', width: '20%' }}>
|
|
||||||
Created by
|
|
||||||
</Table.Th>
|
|
||||||
<Table.Th style={{ textAlign: 'center', width: '10%' }}>Point</Table.Th>
|
|
||||||
</Table.Tr>
|
|
||||||
</Table.Thead>
|
|
||||||
<Table.Tbody>
|
|
||||||
{data.map((row, index) => (
|
|
||||||
<Table.Tr key={index}>
|
|
||||||
<Table.Td style={{ textAlign: 'start' }}>{row.criteria}</Table.Td>
|
|
||||||
<Table.Td style={{ textAlign: 'start' }}>{row.note}</Table.Td>
|
|
||||||
<Table.Td style={{ textAlign: 'start' }}>{row.createdBy}</Table.Td>
|
|
||||||
<Table.Td>{row.point == 0 ? '' : row.point}</Table.Td>
|
|
||||||
</Table.Tr>
|
|
||||||
))}
|
|
||||||
</Table.Tbody>
|
|
||||||
</Table>
|
|
||||||
)
|
|
||||||
|
|
||||||
const isCompactMenu = false
|
const isCompactMenu = false
|
||||||
const Profile = () => {
|
const Profile = () => {
|
||||||
|
|
@ -83,8 +38,6 @@ const Profile = () => {
|
||||||
const [avatar, setAvatar] = useState(user.user.avatar)
|
const [avatar, setAvatar] = useState(user.user.avatar)
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [dataProfile, setDataProfile] = useState<any>([])
|
const [dataProfile, setDataProfile] = useState<any>([])
|
||||||
const [expandedProjects, setExpandedProjects] = useState<ExpandedProjects>({})
|
|
||||||
const [expandedSprints, setExpandedSprints] = useState<ExpandedSprints>({})
|
|
||||||
const [countSpam, setCountSpam] = useState(0)
|
const [countSpam, setCountSpam] = useState(0)
|
||||||
|
|
||||||
const [selectedAvatar, setSelectedAvatar] = useState<string | null>(null)
|
const [selectedAvatar, setSelectedAvatar] = useState<string | null>(null)
|
||||||
|
|
@ -229,22 +182,6 @@ const Profile = () => {
|
||||||
const handleLogout = useCallback(() => {
|
const handleLogout = useCallback(() => {
|
||||||
dispatch(logout(navigate))
|
dispatch(logout(navigate))
|
||||||
}, [dispatch, 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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -364,58 +301,7 @@ const Profile = () => {
|
||||||
|
|
||||||
<Box className={classes.projectInvolvement}>
|
<Box className={classes.projectInvolvement}>
|
||||||
<Title order={3}>Project Involved</Title>
|
<Title order={3}>Project Involved</Title>
|
||||||
<Box className={classes.project} mt="lg">
|
<ProjectInvolvement dataProfile={dataProfile} page="profile" />
|
||||||
{dataProfile.map((project: any) => (
|
|
||||||
<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: any) => (
|
|
||||||
<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: any) => ({
|
|
||||||
...criteria,
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
: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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleSidebar {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar section for user information */
|
||||||
|
.sidebar {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main section for projects and sprints */
|
||||||
|
.projectInvolvement {
|
||||||
|
width: 60%;
|
||||||
|
padding: 0 15px 15px 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: #457bad;
|
||||||
|
color: white;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projectHeader:hover {
|
||||||
|
background-color: var(--hover-background);
|
||||||
|
color: #2d353c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
background-color: rgba(162, 162, 162, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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: 35px; /* 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,274 @@
|
||||||
|
import { getProfilesData } from '@/api/Admin'
|
||||||
|
import DataTableAll from '@/components/DataTable/DataTable'
|
||||||
|
import ProjectInvolvement from '@/components/ProjectInvolvement/ProjectInvolvement'
|
||||||
|
import { get } from '@/rtk/helpers/apiService'
|
||||||
|
import { Box, Button, Select, Text, Title } from '@mantine/core'
|
||||||
|
import { DateInput } from '@mantine/dates'
|
||||||
|
import { notifications } from '@mantine/notifications'
|
||||||
|
import moment from 'moment'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import classes from './StaffEvaluation.module.css'
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
email: string
|
||||||
|
email_verified_at: string | null
|
||||||
|
permission: string
|
||||||
|
remember_token: string | null
|
||||||
|
created_at: string | null
|
||||||
|
updated_at: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Filter {
|
||||||
|
userID: string
|
||||||
|
fromDate: Date | null
|
||||||
|
toDate: Date | null
|
||||||
|
// other properties of the filter object
|
||||||
|
}
|
||||||
|
|
||||||
|
const StaffEvaluation = () => {
|
||||||
|
const [dataProfile, setDataProfile] = useState<any>([])
|
||||||
|
const [listUsers, setListUsers] = useState<User[]>([])
|
||||||
|
const [filter, setFilter] = useState<Filter>({
|
||||||
|
userID: '',
|
||||||
|
fromDate: null,
|
||||||
|
toDate: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(filter, 'filter')
|
||||||
|
|
||||||
|
// const getAllTracking = async () => {
|
||||||
|
// try {
|
||||||
|
// const searchParams = new URLSearchParams(window.location.search)
|
||||||
|
// const params = {}
|
||||||
|
|
||||||
|
// for (const [key, value] of searchParams.entries()) {
|
||||||
|
// if (key === 'page' && value === '') {
|
||||||
|
// Object.assign(params, { [`${key}`]: 1 })
|
||||||
|
// } else {
|
||||||
|
// Object.assign(params, { [`${key}`]: value })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const res = await get(getListTracking, params)
|
||||||
|
// if (res.status) {
|
||||||
|
// setListTracking(res)
|
||||||
|
// }
|
||||||
|
// } catch (error:any) {
|
||||||
|
// notifications.show({
|
||||||
|
// title: 'Error',
|
||||||
|
// message: error.message??error,
|
||||||
|
// color: 'red',
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
const getListProfilesData = async () => {
|
||||||
|
try {
|
||||||
|
const params = {}
|
||||||
|
const res = await get(getProfilesData, params)
|
||||||
|
if (res.status) {
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
notifications.show({
|
||||||
|
title: 'Error',
|
||||||
|
message: error.message ?? error,
|
||||||
|
color: 'red',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
const result = await getListProfilesData()
|
||||||
|
setDataProfile(result ?? [])
|
||||||
|
setListUsers(result ?? [])
|
||||||
|
}
|
||||||
|
fetchData()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
name: 'level',
|
||||||
|
size: '20%',
|
||||||
|
header: 'Level',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
size: '',
|
||||||
|
header: 'Name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'point',
|
||||||
|
size: '10%',
|
||||||
|
header: 'Point',
|
||||||
|
render: (row: any) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{row?.point}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updated_at',
|
||||||
|
size: '25%',
|
||||||
|
header: 'Last update',
|
||||||
|
render: (row: any) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{moment(row?.updated_at).format('HH:mm:ss DD/MM/YYYY')}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const users = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
level: 'Level 1',
|
||||||
|
name: 'React JS',
|
||||||
|
point: 3,
|
||||||
|
created_at: null,
|
||||||
|
updated_at: '2024-09-19T09:08:48.000000Z',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const infoTotal = () => {
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<Text mr={20} fs={'italic'}>
|
||||||
|
Avg:{users.length}
|
||||||
|
</Text>
|
||||||
|
<Text fs={'italic'}>Total: {users.length}</Text>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={classes.title}>
|
||||||
|
<h3>
|
||||||
|
<Text>Admin/</Text>
|
||||||
|
Staff Evaluation
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<Box w="100%" display={'flex'} mt={15} ml={10}>
|
||||||
|
<Box w="50%" display={'flex'}>
|
||||||
|
<Text
|
||||||
|
mr={'xs'}
|
||||||
|
style={{ alignContent: 'center' }}
|
||||||
|
fw={600}
|
||||||
|
size={'md'}
|
||||||
|
>
|
||||||
|
User:
|
||||||
|
</Text>
|
||||||
|
<Select
|
||||||
|
label={''}
|
||||||
|
placeholder="Select user"
|
||||||
|
maxLength={255}
|
||||||
|
required
|
||||||
|
data={listUsers.map((i: User) => i.name)}
|
||||||
|
onChange={(e) => setFilter({ ...filter, userID: e! })}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
w="50%"
|
||||||
|
display={'flex'}
|
||||||
|
style={{ justifyContent: 'flex-end' }}
|
||||||
|
mr={10}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
// m={5}
|
||||||
|
onClick={() => {
|
||||||
|
// setAction('add')
|
||||||
|
// form.reset()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Export
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box className={classes.userInfoSection} display="flex">
|
||||||
|
<Box className={classes.projectInvolvement}>
|
||||||
|
<Box
|
||||||
|
w="100%"
|
||||||
|
display={'flex'}
|
||||||
|
mt={15}
|
||||||
|
style={{ justifyContent: 'space-evenly' }}
|
||||||
|
>
|
||||||
|
<Box display={'flex'} mr={10}>
|
||||||
|
<Text
|
||||||
|
mr={'xs'}
|
||||||
|
style={{ alignContent: 'center' }}
|
||||||
|
fw={600}
|
||||||
|
size={'md'}
|
||||||
|
>
|
||||||
|
From Date:
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<DateInput
|
||||||
|
placeholder="Select date"
|
||||||
|
clearable
|
||||||
|
required
|
||||||
|
label={''}
|
||||||
|
value={filter.fromDate ? new Date(filter.fromDate) : null}
|
||||||
|
valueFormat="DD/MM/YYYY"
|
||||||
|
onChange={(e) => setFilter({ ...filter, fromDate: e! })}
|
||||||
|
></DateInput>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box display={'flex'} mr={10}>
|
||||||
|
<Text
|
||||||
|
mr={'xs'}
|
||||||
|
style={{ alignContent: 'center' }}
|
||||||
|
fw={600}
|
||||||
|
size={'md'}
|
||||||
|
>
|
||||||
|
To Date:
|
||||||
|
</Text>
|
||||||
|
<DateInput
|
||||||
|
placeholder="Select date"
|
||||||
|
clearable
|
||||||
|
required
|
||||||
|
label={''}
|
||||||
|
value={filter.toDate ? new Date(filter.toDate) : null}
|
||||||
|
valueFormat="DD/MM/YYYY"
|
||||||
|
onChange={(e) => setFilter({ ...filter, toDate: e! })}
|
||||||
|
></DateInput>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<ProjectInvolvement dataProfile={dataProfile} page="admin" />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box className={classes.sidebar}>
|
||||||
|
<Title order={3} className={classes.titleSidebar}>
|
||||||
|
Technicals
|
||||||
|
</Title>
|
||||||
|
<DataTableAll
|
||||||
|
data={users}
|
||||||
|
columns={columns}
|
||||||
|
size=""
|
||||||
|
searchInput
|
||||||
|
infoTotal={infoTotal()}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StaffEvaluation
|
||||||
|
|
@ -8,6 +8,7 @@ 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 Profile from '@/pages/Profile/Profile'
|
||||||
import SprintReview from '@/pages/SprintReview/SprintReview'
|
import SprintReview from '@/pages/SprintReview/SprintReview'
|
||||||
|
import StaffEvaluation from '@/pages/StaffEvaluation/StaffEvaluation'
|
||||||
import TestReport from '@/pages/TestReport/TestReport'
|
import TestReport from '@/pages/TestReport/TestReport'
|
||||||
import Tickets from '@/pages/Tickets/Tickets'
|
import Tickets from '@/pages/Tickets/Tickets'
|
||||||
import TicketsManagement from '@/pages/TicketsManagement/TicketsManagement'
|
import TicketsManagement from '@/pages/TicketsManagement/TicketsManagement'
|
||||||
|
|
@ -204,6 +205,20 @@ const mainRoutes = [
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/staff-avaluation',
|
||||||
|
element: (
|
||||||
|
<ProtectedRoute mode="route" permission="admin">
|
||||||
|
<BasePage
|
||||||
|
main={
|
||||||
|
<>
|
||||||
|
<StaffEvaluation />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
></BasePage>
|
||||||
|
</ProtectedRoute>
|
||||||
|
),
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// path: '/packages',
|
// path: '/packages',
|
||||||
// element: (
|
// element: (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue