From 6d4190d8a2580a9bd8758f87678f0ea4361f3b33 Mon Sep 17 00:00:00 2001 From: Truong Vo <41848815+vmtruong301296@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:50:53 +0700 Subject: [PATCH 1/7] =?UTF-8?q?Th=E1=BB=B1c=20hi=E1=BB=87n=20page=20staff?= =?UTF-8?q?=20evaluation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/DataTable/DataTable.tsx | 16 +- FRONTEND/src/components/Navbar/Navbar.tsx | 9 +- .../ProjectInvolvement.module.css | 58 ++++ .../ProjectInvolvement/ProjectInvolvement.tsx | 145 +++++++++ FRONTEND/src/pages/Profile/Profile.tsx | 122 +------- .../StaffEvaluation.module.css | 202 +++++++++++++ .../pages/StaffEvaluation/StaffEvaluation.tsx | 274 ++++++++++++++++++ FRONTEND/src/routes/main.tsx | 15 + 8 files changed, 719 insertions(+), 122 deletions(-) create mode 100644 FRONTEND/src/components/ProjectInvolvement/ProjectInvolvement.module.css create mode 100644 FRONTEND/src/components/ProjectInvolvement/ProjectInvolvement.tsx create mode 100644 FRONTEND/src/pages/StaffEvaluation/StaffEvaluation.module.css create mode 100644 FRONTEND/src/pages/StaffEvaluation/StaffEvaluation.tsx diff --git a/FRONTEND/src/components/DataTable/DataTable.tsx b/FRONTEND/src/components/DataTable/DataTable.tsx index d64fe60..b51b9f7 100755 --- a/FRONTEND/src/components/DataTable/DataTable.tsx +++ b/FRONTEND/src/components/DataTable/DataTable.tsx @@ -83,6 +83,7 @@ export const DataTableAll = ({ searchInput, checkBox, size, + infoTotal, }: { data: any[] columns: Column[] @@ -90,6 +91,7 @@ export const DataTableAll = ({ searchInput?: boolean checkBox?: boolean size: string + infoTotal?: React.ReactNode // Set the type to ReactNode to allow JSX elements }) => { const [Tdata, setTData] = useState(data) // const [tempData, setTempData] = useState([]) @@ -323,7 +325,14 @@ export const DataTableAll = ({ )} - + + + {infoTotal} + + { + const removeParam = (name: string) => { // Create a URL object let url = new URL(window.location.href) @@ -682,7 +691,8 @@ export const DataTablePagination = ({ search: urlParams.toString(), }) } - statusSort.status === 'clear' && removeParam(`order_by_${statusSort.name}`) + statusSort.status === 'clear' && + removeParam(`order_by_${statusSort.name}`) } catch (error) { console.warn(error) } diff --git a/FRONTEND/src/components/Navbar/Navbar.tsx b/FRONTEND/src/components/Navbar/Navbar.tsx index 78e7b07..48e4687 100755 --- a/FRONTEND/src/components/Navbar/Navbar.tsx +++ b/FRONTEND/src/components/Navbar/Navbar.tsx @@ -24,6 +24,7 @@ import { IconBinaryTree2, IconCalendar, IconCalendarClock, + IconChartDots2, IconDevices, IconLayoutSidebarLeftExpand, IconLayoutSidebarRightExpand, @@ -37,7 +38,7 @@ import { IconSun, IconTicket, IconUsersGroup, - IconZoomExclamation, + IconZoomExclamation } from '@tabler/icons-react' import { useCallback, useEffect, useState } from 'react' import { useLocation, useNavigate } from 'react-router-dom' @@ -107,6 +108,12 @@ const data = [ icon: IconZoomExclamation, group: 'hidden', }, + { + link: '/staff-avaluation', + label: 'Staff evaluation', + icon: IconChartDots2, + group: 'admin', + }, // { link: '/jira', label: 'Jira', icon: IconSubtask }, // { link: '/custom-theme', label: 'Custom Theme', icon: IconBrush }, // { link: '/general-setting', label: 'General Setting', icon: IconSettings }, diff --git a/FRONTEND/src/components/ProjectInvolvement/ProjectInvolvement.module.css b/FRONTEND/src/components/ProjectInvolvement/ProjectInvolvement.module.css new file mode 100644 index 0000000..fec696e --- /dev/null +++ b/FRONTEND/src/components/ProjectInvolvement/ProjectInvolvement.module.css @@ -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; +} diff --git a/FRONTEND/src/components/ProjectInvolvement/ProjectInvolvement.tsx b/FRONTEND/src/components/ProjectInvolvement/ProjectInvolvement.tsx new file mode 100644 index 0000000..049b5d8 --- /dev/null +++ b/FRONTEND/src/components/ProjectInvolvement/ProjectInvolvement.tsx @@ -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 = ({ data, page }) => ( + + + + + Criteria + + Note + + Created by + + {page == 'profile' ? ( + '' + ) : ( + + Point + + )} + + + + {data.map((row, index) => ( + + {row.criteria} + {row.note} + {row.createdBy} + {page == 'profile' ? ( + '' + ) : ( + {row.point == 0 ? '' : row.point} + )} + + ))} + +
+) + +const ProjectInvolvement = ({ dataProfile, page }: ProjectInvolvementProps) => { + const [expandedProjects, setExpandedProjects] = useState({}) + const [expandedSprints, setExpandedSprints] = useState({}) + + 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 ( + + {dataProfile.map((project) => ( +
+ handleProjectToggle(project.name)} + > + + {project.name} + + + + {expandedProjects[project.name] && ( + + {project.sprints.map((sprint) => ( +
+ + handleSprintToggle(project.name, sprint.name) + } + > + + + {sprint.name} + + + + {expandedSprints[project.name]?.[sprint.name] && ( + + + + )} +
+ ))} +
+ )} +
+ ))} +
+ ) +} + +export default ProjectInvolvement diff --git a/FRONTEND/src/pages/Profile/Profile.tsx b/FRONTEND/src/pages/Profile/Profile.tsx index 9f21c24..2b62ade 100644 --- a/FRONTEND/src/pages/Profile/Profile.tsx +++ b/FRONTEND/src/pages/Profile/Profile.tsx @@ -1,73 +1,28 @@ import { getProfilesData, updateProfilesData } from '@/api/Admin' import { changePassword } from '@/api/Auth' import PasswordRequirementInput from '@/components/PasswordRequirementInput/PasswordRequirementInput' +import ProjectInvolvement from '@/components/ProjectInvolvement/ProjectInvolvement' import { logout } from '@/rtk/dispatches/auth' import { get, post, postImage } from '@/rtk/helpers/apiService' import { requirementsPassword } from '@/rtk/helpers/variables' import { useAppDispatch, useAppSelector } from '@/rtk/hooks' import { getUser } from '@/rtk/localStorage' +import { success } from '@/rtk/slices/auth' 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 { IconPasswordUser } from '@tabler/icons-react' import { useCallback, useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' 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[] }) => ( - - - - - Criteria - - Note - - Created by - - Point - - - - {data.map((row, index) => ( - - {row.criteria} - {row.note} - {row.createdBy} - {row.point == 0 ? '' : row.point} - - ))} - -
-) const isCompactMenu = false const Profile = () => { @@ -83,8 +38,6 @@ const Profile = () => { const [avatar, setAvatar] = useState(user.user.avatar) const [loading, setLoading] = useState(false) const [dataProfile, setDataProfile] = useState([]) - const [expandedProjects, setExpandedProjects] = useState({}) - const [expandedSprints, setExpandedSprints] = useState({}) const [countSpam, setCountSpam] = useState(0) const [selectedAvatar, setSelectedAvatar] = useState(null) @@ -229,22 +182,6 @@ const Profile = () => { 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 (
@@ -364,58 +301,7 @@ const Profile = () => { Project Involved - - {dataProfile.map((project: any) => ( -
- {/* Project Header */} - handleProjectToggle(project.name)} - > - - {project.name} - - - - {/* Project's Sprints */} - {expandedProjects[project.name] && ( - - {project.sprints.map((sprint: any) => ( -
- - handleSprintToggle(project.name, sprint.name) - } - > - - - {sprint.name} - - - - {/* Criteria Table */} - {expandedSprints[project.name]?.[sprint.name] && ( - - {/* */} - ({ - ...criteria, - }))} - /> - - )} -
- ))} -
- )} -
- ))} -
+
diff --git a/FRONTEND/src/pages/StaffEvaluation/StaffEvaluation.module.css b/FRONTEND/src/pages/StaffEvaluation/StaffEvaluation.module.css new file mode 100644 index 0000000..86122c0 --- /dev/null +++ b/FRONTEND/src/pages/StaffEvaluation/StaffEvaluation.module.css @@ -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); +} diff --git a/FRONTEND/src/pages/StaffEvaluation/StaffEvaluation.tsx b/FRONTEND/src/pages/StaffEvaluation/StaffEvaluation.tsx new file mode 100644 index 0000000..74a5971 --- /dev/null +++ b/FRONTEND/src/pages/StaffEvaluation/StaffEvaluation.tsx @@ -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([]) + const [listUsers, setListUsers] = useState([]) + const [filter, setFilter] = useState({ + 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 ( +
+ {row?.point} +
+ ) + }, + }, + { + name: 'updated_at', + size: '25%', + header: 'Last update', + render: (row: any) => { + return ( +
+ {moment(row?.updated_at).format('HH:mm:ss DD/MM/YYYY')} +
+ ) + }, + }, + ] + + 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 ( +
+ + Avg:{users.length} + + Total: {users.length} +
+ ) + } + return ( +
+
+

+ Admin/ + Staff Evaluation +

+
+ + + + User: + + i.name)} + data={listUsers.map((i: User) => ({ + value: i.id.toString(), + label: i.name, + }))} + value={filter.userID} onChange={(e) => setFilter({ ...filter, userID: e! })} /> @@ -251,20 +323,50 @@ const StaffEvaluation = () => { > - + + + + Loading . . . + + + {loading ? null : ( + + )} Technicals - + {loadingTechnical ? ( + + + + Loading . . . + + + ) : ( + + )}
From 8ebe9eb57f490c2f59258ecc11c00ffcae31fc09 Mon Sep 17 00:00:00 2001 From: Truong Vo <41848815+vmtruong301296@users.noreply.github.com> Date: Fri, 20 Sep 2024 17:12:24 +0700 Subject: [PATCH 6/7] =?UTF-8?q?C=E1=BA=ADp=20nh=E1=BA=ADt=20l=E1=BA=A1i=20?= =?UTF-8?q?filter=20api=20theo=20ng=C3=A0y=20ho=C3=A0n=20th=C3=A0nh=20spri?= =?UTF-8?q?nt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Http/Controllers/EvaluationController.php | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/EvaluationController.php b/BACKEND/Modules/Admin/app/Http/Controllers/EvaluationController.php index 788236c..e893521 100644 --- a/BACKEND/Modules/Admin/app/Http/Controllers/EvaluationController.php +++ b/BACKEND/Modules/Admin/app/Http/Controllers/EvaluationController.php @@ -33,22 +33,33 @@ class EvaluationController extends Controller $projects = $this->jiraService->getAllProjects(); - $query = UserCriteria::where('user_email', $userEmail); - - // Date filter - if ($request->filled('fromDate')) { - $fromDate = Carbon::parse($request->input('fromDate')); - $query->where('created_at', '>=', $fromDate); - } - - if ($request->filled('toDate')) { - $toDate = Carbon::parse($request->input('toDate')); - $query->where('created_at', '<=', $toDate); - } - - // End query - $userCriterias = $query->with(['sprint', 'criteria'])->get(); + $startDate = $request->input('fromDate'); + $endDate = $request->input('toDate'); + $userCriterias = UserCriteria::with([ + 'sprint' => function ($query) use ($startDate, $endDate) { + if ($startDate && $endDate) { + $query->whereBetween('complete_date', [$startDate, $endDate]); + } elseif ($startDate) { + $query->where('complete_date', '>=', $startDate); + } elseif ($endDate) { + $query->where('complete_date', '<=', $endDate); + } + }, + 'criteria', + ])->where('user_email', $userEmail) + ->whereHas('sprint', function ($query) use ($startDate, $endDate) { + if ($startDate && $endDate) { + $query->whereBetween('complete_date', [$startDate, $endDate]); + } elseif ($startDate) { + $query->where('complete_date', '>=', $startDate); + } elseif ($endDate) { + $query->where('complete_date', '<=', $endDate); + } + }) + ->get(); + // dd($userCriterias); + // Xử lý dữ liệu thành cấu trúc mong muốn $projectsData = $userCriterias->groupBy('sprint.project_id')->map(function ($userCriteriasByProject, $projectId) use ($projects) { $result = self::getProjectById($projects, $projectId); return [ @@ -57,12 +68,13 @@ class EvaluationController extends Controller $sprint = $userCriteriasBySprint->first()->sprint; return [ 'name' => $sprint->name, + 'complete_date' => $sprint->complete_date ?? '', 'criterias' => $userCriteriasBySprint->map(function ($userCriteria) { $criteria = $userCriteria->criteria; return [ 'criteria' => $criteria->name, 'note' => $userCriteria->note ?? '', - 'createdBy' => $userCriteria->created_by ?? '', + 'createdBy' => $userCriteria->created_by ?? '', // Lấy tên user từ auth 'point' => $userCriteria->point ?? '', ]; }) @@ -71,6 +83,7 @@ class EvaluationController extends Controller ]; })->values(); + // Trả về kết quả return AbstractController::ResultSuccess($projectsData); } From 0fe9228241f53fc049622ded9c8a6933fb83ffda Mon Sep 17 00:00:00 2001 From: Truong Vo <41848815+vmtruong301296@users.noreply.github.com> Date: Fri, 20 Sep 2024 17:54:46 +0700 Subject: [PATCH 7/7] =?UTF-8?q?Intergate=20ch=E1=BB=A9c=20n=C4=83ng=20staf?= =?UTF-8?q?f=20evaluation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Http/Controllers/TechnicalController.php | 28 +++++++++++++------ FRONTEND/src/api/Admin.ts | 1 + .../pages/StaffEvaluation/StaffEvaluation.tsx | 25 +++++++++++++---- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/TechnicalController.php b/BACKEND/Modules/Admin/app/Http/Controllers/TechnicalController.php index c3e9cba..68ac93d 100644 --- a/BACKEND/Modules/Admin/app/Http/Controllers/TechnicalController.php +++ b/BACKEND/Modules/Admin/app/Http/Controllers/TechnicalController.php @@ -89,23 +89,33 @@ class TechnicalController extends Controller ->orderBy('point', 'desc') ->get(); - if ($technicals->isEmpty()) { - return AbstractController::ResultError("No technicals found for this user."); + // Lấy tất cả các technical + $allTechnicals = Technical::all(); + + if ($technicals->isEmpty() && $allTechnicals->isEmpty()) { + return AbstractController::ResultError("No technicals found."); } // Chuẩn bị dữ liệu để trả về - $technicalData = $technicals->map(function ($technicalUser) { + $technicalData = $allTechnicals->map(function ($technical) use ($technicals) { + // Tìm kiếm kỹ thuật từ bảng technical_users + $technicalUser = $technicals->firstWhere('technical_id', $technical->id); + return [ - 'id' => $technicalUser->technical->id, - 'name' => $technicalUser->technical->name, - 'level' => $technicalUser->technical->level, - 'point' => $technicalUser->point, - 'updated_at' => $technicalUser->updated_at + 'id' => $technical->id, + 'name' => $technical->name, + 'level' => $technical->level, + 'point' => $technicalUser ? $technicalUser->point : 0, // Nếu không tồn tại, điểm mặc định là 0 + 'updated_at' => $technicalUser ? $technicalUser->updated_at : null // Nếu không tồn tại, updated_at là null ]; }); - return AbstractController::ResultSuccess($technicalData); + $sortedTechnicalData = $technicalData->sortByDesc('point')->values(); // values() để giữ lại chỉ số + + + return AbstractController::ResultSuccess($sortedTechnicalData); } + public function getTechnicalsOfUser() { $userInfo = auth('admins')->user(); diff --git a/FRONTEND/src/api/Admin.ts b/FRONTEND/src/api/Admin.ts index 2b2f8e3..3302df4 100755 --- a/FRONTEND/src/api/Admin.ts +++ b/FRONTEND/src/api/Admin.ts @@ -81,3 +81,4 @@ export const getAllTechByUserId = API_URL + 'v1/admin/technical/get-tech-by-user-id' export const evaluation = API_URL + 'v1/admin/evaluation/report' +export const sprintReview = API_URL + 'v1/admin/evaluation/sprint-review' diff --git a/FRONTEND/src/pages/StaffEvaluation/StaffEvaluation.tsx b/FRONTEND/src/pages/StaffEvaluation/StaffEvaluation.tsx index 4a151ec..73a3354 100644 --- a/FRONTEND/src/pages/StaffEvaluation/StaffEvaluation.tsx +++ b/FRONTEND/src/pages/StaffEvaluation/StaffEvaluation.tsx @@ -1,8 +1,8 @@ import { + evaluation, getAllTechByUserId, getAllUser, - getProfilesData, - evaluation, + sprintReview } from '@/api/Admin' import DataTableAll from '@/components/DataTable/DataTable' import ProjectInvolvement from '@/components/ProjectInvolvement/ProjectInvolvement' @@ -10,10 +10,10 @@ import { get } from '@/rtk/helpers/apiService' import { Box, Button, Loader, Select, Text, Title } from '@mantine/core' import { DateInput } from '@mantine/dates' import { notifications } from '@mantine/notifications' +import axios from 'axios' import moment from 'moment' import { useEffect, useState } from 'react' import classes from './StaffEvaluation.module.css' -import axios from 'axios' interface User { id: number @@ -122,7 +122,7 @@ const StaffEvaluation = () => { ? moment(filterSearch.toDate).format('YYYY-MM-DD') : null, } - const res = await get(getProfilesData, params) + const res = await get(sprintReview, params) if (res.status) { return res.data } @@ -175,7 +175,7 @@ const StaffEvaluation = () => { } fetchData() } - }, [filter]) + }, [filter?.userID]) const columns = [ { @@ -370,7 +370,20 @@ const StaffEvaluation = () => { Loading . . . - {loading ? null : ( + {!loading && dataProfile.length == 0 && ( + + + No Data Sprint + + + )} + {!loading && ( )}