From bb40261a0d13ebb8ab8f226ffacc3fa817f6b4e0 Mon Sep 17 00:00:00 2001 From: dbdbd9 Date: Sat, 21 Sep 2024 10:19:04 +0700 Subject: [PATCH] Technical: add Profile page, Organization Settings page --- FRONTEND/src/api/Admin.ts | 8 + FRONTEND/src/components/Navbar/Navbar.tsx | 9 +- .../OrganizationSettings.module.css | 10 + .../OrganizationSettings.tsx | 322 ++++++++++++++++++ FRONTEND/src/pages/Profile/Profile.tsx | 295 +++++++++++++++- FRONTEND/src/routes/main.tsx | 15 + 6 files changed, 643 insertions(+), 16 deletions(-) create mode 100644 FRONTEND/src/pages/OrganizationSettings/OrganizationSettings.module.css create mode 100644 FRONTEND/src/pages/OrganizationSettings/OrganizationSettings.tsx diff --git a/FRONTEND/src/api/Admin.ts b/FRONTEND/src/api/Admin.ts index 3302df4..23b8fcb 100755 --- a/FRONTEND/src/api/Admin.ts +++ b/FRONTEND/src/api/Admin.ts @@ -76,9 +76,17 @@ export const getProfilesData = API_URL + 'v1/admin/criterias/profiles-data' export const updateProfilesData = API_URL + 'v1/admin/criterias/profiles-data/update' +export const listUserTechnical = API_URL + 'v1/admin/technical/get-tech-of-user' +export const updateUserTechnical = API_URL + 'v1/admin/technical/technicals-user/update' + export const getAllUser = API_URL + 'v1/admin/technical/get-all-user' 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' + +//Technical +export const listTechnical = API_URL + 'v1/admin/technical/get-all' +export const createTechnical = API_URL + 'v1/admin/technical/create' +export const deleteTechnical = API_URL + 'v1/admin/technical/delete' diff --git a/FRONTEND/src/components/Navbar/Navbar.tsx b/FRONTEND/src/components/Navbar/Navbar.tsx index 48e4687..a4eeba2 100755 --- a/FRONTEND/src/components/Navbar/Navbar.tsx +++ b/FRONTEND/src/components/Navbar/Navbar.tsx @@ -35,10 +35,11 @@ import { IconQrcode, IconReport, IconScan, + IconSettings, IconSun, IconTicket, IconUsersGroup, - IconZoomExclamation + IconZoomExclamation, } from '@tabler/icons-react' import { useCallback, useEffect, useState } from 'react' import { useLocation, useNavigate } from 'react-router-dom' @@ -114,6 +115,12 @@ const data = [ icon: IconChartDots2, group: 'admin', }, + { + link: '/organization-settings', + label: 'Organization Settings', + icon: IconSettings, + 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/pages/OrganizationSettings/OrganizationSettings.module.css b/FRONTEND/src/pages/OrganizationSettings/OrganizationSettings.module.css new file mode 100644 index 0000000..fabb1bd --- /dev/null +++ b/FRONTEND/src/pages/OrganizationSettings/OrganizationSettings.module.css @@ -0,0 +1,10 @@ +.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; +} \ No newline at end of file diff --git a/FRONTEND/src/pages/OrganizationSettings/OrganizationSettings.tsx b/FRONTEND/src/pages/OrganizationSettings/OrganizationSettings.tsx new file mode 100644 index 0000000..1bb5620 --- /dev/null +++ b/FRONTEND/src/pages/OrganizationSettings/OrganizationSettings.tsx @@ -0,0 +1,322 @@ +import { useEffect, useState } from 'react' +import { + Box, + Button, + Dialog, + Flex, + Group, + Loader, + Modal, + Tabs, + Text, + TextInput, +} from '@mantine/core' +import classes from './OrganizationSettings.module.css' +import DataTableAll from '@/components/DataTable/DataTable' +import { get, post } from '@/rtk/helpers/apiService' +import { notifications } from '@mantine/notifications' +import { createTechnical, deleteTechnical, listTechnical } from '@/api/Admin' +import { useForm } from '@mantine/form' +import { Xdelete } from '@/rtk/helpers/CRUD' +import moment from 'moment' + +interface DataTechnical { + id: number + name: string + level: number + point: number + updated_at: string +} + +function OrganizationSettings() { + const [activeTab, setActiveTab] = useState('technical') + const [isLoading, setIsLoading] = useState(false) + const [addTechnicalOpen, setAddTechnicalOpen] = useState(false) + const [deleteTechnicalOpen, setDeleteTechnicalOpen] = useState(false) + const [selectedId, setSelectedId] = useState(0) + + const [dataTechnical, setDataTechnical] = useState([]) + + const form = useForm({ + initialValues: { + name: '', + level: '', + }, + }) + + useEffect(() => { + const fetchData = async () => { + await getListTechnical() + } + fetchData() + }, []) + + const columns = [ + { + name: 'level', + size: '5%', + header: 'Level', + render: (row: any) => { + return ( + + {row?.level ? row.level : ''} + + ) + }, + }, + { + name: 'name', + size: '40%', + header: 'Name', + }, + { + name: 'created_at', + size: '40%', + header: 'Created at', + render: (row: any) => { + return ( + {moment(row?.created_at).format('HH:mm:ss DD/MM/YYYY')} + ) + }, + }, + { + name: 'action', + size: '15%', + header: 'Action', + render: (row: any) => { + return ( + + + + ) + }, + }, + ] + + const getListTechnical = async () => { + try { + setIsLoading(true) + const params = {} + const res = await get(listTechnical, params) + if (res.status) { + setDataTechnical(res.data) + } + } catch (error: any) { + notifications.show({ + title: 'Error', + message: error.message ?? error, + color: 'red', + }) + } finally { + setIsLoading(false) + } + + return [] + } + + const handleCreate = async (values: any) => { + try { + const { id, ...data } = values + const res = await post(createTechnical, data) + if (res.status === true) { + setAddTechnicalOpen(false) + form.reset() + await getListTechnical() + + notifications.show({ + title: 'Success', + message: res.message, + color: 'green', + }) + } + } catch (error) { + console.log(error) + } + } + + const handleDelete = async (id: number) => { + try { + await Xdelete(deleteTechnical, { id: id }, getListTechnical) + setSelectedId(0) + setDeleteTechnicalOpen(false) + } catch (error) { + console.log(error) + } + } + + return ( +
+
+

+ Admin/ + Staff Evaluation +

+
+ + + + + Technical setting + Setting 2 + Setting 3 + + + + + Note: + + + + Level 1: 3-12 Month + + + Level 2: 3-5 Year + + + Level 3: 5 -8 Year + + + + + + {isLoading ? ( + + + + Loading Technical... + + + ) : ( + setAddTechnicalOpen(true)}> + + Add + + } + /> + )} + + + + + Setting 2 + + + + Setting 3 + + + + + { + setAddTechnicalOpen(false) + form.reset() + }} + title={ + + Add Technical + + } + > +
{ + await handleCreate(values) + })} + > + + form.setFieldValue('name', e.target.value)} + /> + + form.setFieldValue('level', e.target.value)} + /> + + + + + +
+
+ + setDeleteTechnicalOpen(false)} + size="lg" + radius="md" + position={{ top: 30, right: 10 }} + > + + Do you want to delete this technical? + + + + + + +
+ ) +} + +export default OrganizationSettings diff --git a/FRONTEND/src/pages/Profile/Profile.tsx b/FRONTEND/src/pages/Profile/Profile.tsx index 2b62ade..d22f7b1 100644 --- a/FRONTEND/src/pages/Profile/Profile.tsx +++ b/FRONTEND/src/pages/Profile/Profile.tsx @@ -1,4 +1,9 @@ -import { getProfilesData, updateProfilesData } from '@/api/Admin' +import { + getProfilesData, + listUserTechnical, + updateProfilesData, + updateUserTechnical, +} from '@/api/Admin' import { changePassword } from '@/api/Auth' import PasswordRequirementInput from '@/components/PasswordRequirementInput/PasswordRequirementInput' import ProjectInvolvement from '@/components/ProjectInvolvement/ProjectInvolvement' @@ -12,6 +17,8 @@ import { Avatar, Box, Button, + Flex, + Loader, Modal, PasswordInput, Text, @@ -19,12 +26,32 @@ import { Title, } from '@mantine/core' import { notifications } from '@mantine/notifications' -import { IconPasswordUser } from '@tabler/icons-react' +import { + IconPasswordUser, + IconUserCode, + IconUserCog, +} from '@tabler/icons-react' import { useCallback, useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' import classes from './Profile.module.css' +import DataTableAll from '@/components/DataTable/DataTable' +import moment from 'moment' const isCompactMenu = false + +interface DataTechnical { + id: number + name: string + level: number + point: number + updated_at: string +} + +interface UpdateDataTechnical { + technical_id: number | string + point: number | string +} + const Profile = () => { const user = useAppSelector((state) => state.authentication.user) const userData = getUser() @@ -35,11 +62,22 @@ const Profile = () => { new_password: '', confirm_password: '', }) + + const [swapTap, setSwapTab] = useState(false) + const [avatar, setAvatar] = useState(user.user.avatar) const [loading, setLoading] = useState(false) const [dataProfile, setDataProfile] = useState([]) const [countSpam, setCountSpam] = useState(0) + const [dataTechnical, setDataTechnical] = useState([]) + const [loadingTechnical, setLoadingTechnical] = useState(false) + const [isUpdateTechnical, setIsUpdateTechnical] = useState(false) + + const [updatedDataTechnical, setUpdatedDataTechnical] = useState< + UpdateDataTechnical[] + >([]) + const [selectedAvatar, setSelectedAvatar] = useState(null) const navigate = useNavigate() const dispatch = useAppDispatch() @@ -183,6 +221,142 @@ const Profile = () => { dispatch(logout(navigate)) }, [dispatch, navigate]) + useEffect(() => { + const fetchData = async () => { + await getListUserTechnical() + } + fetchData() + }, []) + + const columns = [ + { + name: 'level', + size: '5%', + header: 'Level', + render: (row: any) => { + return ( + + {row?.level ? row.level : ''} + + ) + }, + }, + { + name: 'name', + size: '50%', + header: 'Name', + render: (row: any) => { + return {row?.name} + }, + }, + { + name: 'point', + size: '5%', + header: 'Point', + render: (row: any) => { + return ( + + technicalItem.technical_id === row?.id, + )?.point + } + onChange={(e) => { + setUpdatedDataTechnical((prev: any) => + prev.map((technicalItem: UpdateDataTechnical) => { + if (technicalItem.technical_id === row.id) { + return { + ...technicalItem, + point: e.target.value, + } + } + + return technicalItem + }), + ) + setIsUpdateTechnical(true) + }} + /> + ) + }, + }, + { + name: 'updated_at', + size: '40%', + header: 'Last update', + render: (row: any) => { + return ( + + {moment(row?.updated_at).format('HH:mm:ss DD/MM/YYYY')} + + ) + }, + }, + ] + + const getListUserTechnical = async () => { + try { + setLoadingTechnical(true) + const params = {} + const res = await get(listUserTechnical, params) + if (res.status) { + setDataTechnical(res.data) + setUpdatedDataTechnical( + res.data?.map((technicalItem: DataTechnical) => { + return { + technical_id: technicalItem.id, + point: technicalItem.point, + } + }), + ) + } + } catch (error: any) { + notifications.show({ + title: 'Error', + message: error.message ?? error, + color: 'red', + }) + } finally { + setLoadingTechnical(false) + } + + return [] + } + + const handleUpdateTechnical = async () => { + try { + const res = await post(updateUserTechnical, { + technicals: updatedDataTechnical, + }) + if (res.status === true) { + await getListUserTechnical() + + notifications.show({ + title: 'Success', + message: res.message, + color: 'green', + }) + } + } catch (error) { + console.log(error) + } + } + return (
@@ -281,28 +455,119 @@ const Profile = () => { display: 'flex', flexFlow: 'column', alignItems: 'center', + gap: 8, }} mt={10} > - setOpened(true)} + color="green" > - - + Change password + + + {swapTap ? ( + + ) : ( + + )} - - Project Involved - - + {swapTap ? ( + + + + Level: + + + + 1: 3-12 Month + + + 2: 3-5 Year + + + 3: 5 -8 Year + + + + + + Point: + + + + 0: Unknown + + + 1: Basic + + + 2: Advanced + + + 3: Master + + + + + + +
+ Technicals + + +
+ + {loadingTechnical ? ( + + + + Loading Technical... + + + ) : ( + + )} +
+ ) : ( + + Project Involved + + + )} ), }, + { + path: '/organization-settings', + element: ( + + + + + } + > + + ), + }, // { // path: '/packages', // element: (