import { evaluation, getAllTechByUserId, getAllUser, projectReview, sprintReview, projectReviewAdd, projectReviewUpdate, projectReviewDelete, evaluationReportAllUsers, getListTrackingSummary, getPJParticipating, } from '@/api/Admin' import DataTableAll from '@/components/DataTable/DataTable' import ProjectInvolvement from '@/components/ProjectInvolvement/ProjectInvolvement' import { get, getDownloadFile, post } from '@/rtk/helpers/apiService' import { Box, Button, Dialog, Group, Loader, Modal, Select, Tabs, Text, Textarea, TextInput, 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' import { IconClearAll, IconEdit, IconPresentationAnalytics, IconReportAnalytics, IconX, } from '@tabler/icons-react' import { useForm } from '@mantine/form' import { update, Xdelete } from '@/rtk/helpers/CRUD' import { PieChart } from '@mantine/charts' 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 } interface DataTechnical { id: number name: string level: number point: number updated_at: string } interface DataProjectReview { id: number name: string role: string note: string user_id: number created_at: string updated_at: string } interface DataPJParticipating { name: string total_task: number total_time_spent: number } type TLog = { id: number name: string status: string time_string: Date } interface DataSummaryTracking { on_time_morning: number late_morning: number on_time_afternoon: number late_afternoon: number value: TLog[] } const StaffEvaluation = () => { const [loading, setLoading] = useState(false) const [loadingReview, setLoadingReview] = useState(false) const [loadingWorkingStyle, setLoadingWorkingStyle] = useState(false) const [loadingPJParticipating, setLoadingPJParticipating] = useState(false) const [loadingTechnical, setLoadingTechnical] = useState(false) const [dataProfile, setDataProfile] = useState([]) const [dataTechnical, setDataTechnical] = useState([]) const [dataProjectReview, setDataProjectReview] = useState< DataProjectReview[] >([]) const [listUsers, setListUsers] = useState([]) const [filter, setFilter] = useState({ userID: '', fromDate: null, toDate: null, }) const [action, setAction] = useState('') const [item, setItem] = useState({ id: 0 }) const [disableBtn, setDisableBtn] = useState(false) const [activeBtn, setActiveBtn] = useState(false) const [loadingExport, setLoadingExport] = useState(false) const [loadingExportAll, setLoadingExportAll] = useState(false) const [dataPJParticipating, setDataPJParticipating] = useState< DataPJParticipating[] >([]) const [dataSummaryTracking, setDataSummaryTracking] = useState({ on_time_morning: 0, late_morning: 0, on_time_afternoon: 0, late_afternoon: 0, value: [], }) const form = useForm({ initialValues: { id: 0, name: '', role: '', note: '', user_id: 0, created_at: '', updated_at: '', }, validate: { name: (value) => value.length === 0 ? 'Please enter project name' : null, role: (value) => (value.length === 0 ? 'Please enter role' : null), note: (value) => (value.length === 0 ? 'Please enter note' : null), }, }) const getListUser = async () => { try { const params = {} const res = await get(getAllUser, params) if (res.status) { return res.data } } catch (error: any) { notifications.show({ title: 'Error', message: error.message ?? error, color: 'red', }) } return [] } function getFormattedDateTime(): string { const now = new Date() const year = now.getFullYear() const month = String(now.getMonth() + 1).padStart(2, '0') // Tháng bắt đầu từ 0 const day = String(now.getDate()).padStart(2, '0') const hours = String(now.getHours()).padStart(2, '0') const minutes = String(now.getMinutes()).padStart(2, '0') const seconds = String(now.getSeconds()).padStart(2, '0') return `${year}${month}${day}${hours}${minutes}${seconds}` } const downloadFile = async (filterSearch: Filter) => { try { const params = { userID: filterSearch.userID ?? '', fromDate: filterSearch.fromDate ? moment(filterSearch.fromDate).format('YYYY-MM-DD') : null, toDate: filterSearch.toDate ? moment(filterSearch.toDate).format('YYYY-MM-DD') : null, } const user = listUsers.find( (el) => el.id.toString() === filterSearch.userID, ) setLoadingExport(true) const res = await getDownloadFile(evaluation, params) if (res.status) { const fileURL = window.URL.createObjectURL(new Blob([res.data])) const fileLink = document.createElement('a') const fileName = `STAFF_EVALUATION_${user?.name ?.split(' ') .join('_')}_${getFormattedDateTime()}.docx` fileLink.href = fileURL fileLink.setAttribute('download', fileName) document.body.appendChild(fileLink) fileLink.click() fileLink.remove() } setLoadingExport(false) } catch (error: any) { notifications.show({ title: 'Error', message: error.message ?? error, color: 'red', }) } return [] } const downloadFileAll = async (filterSearch: Filter) => { try { const params = { fromDate: filterSearch.fromDate ? moment(filterSearch.fromDate).format('YYYY-MM-DD') : null, toDate: filterSearch.toDate ? moment(filterSearch.toDate).format('YYYY-MM-DD') : null, } setLoadingExportAll(true) const res = await getDownloadFile(evaluationReportAllUsers, params) if (res.status) { const fileURL = window.URL.createObjectURL(new Blob([res.data])) const fileLink = document.createElement('a') const fileName = `STAFF_EVALUATION_All_USERS_${getFormattedDateTime()}.docx` fileLink.href = fileURL fileLink.setAttribute('download', fileName) document.body.appendChild(fileLink) fileLink.click() fileLink.remove() } setLoadingExportAll(false) } catch (error: any) { notifications.show({ title: 'Error', message: error.message ?? error, color: 'red', }) } return [] } useEffect(() => { const fetchData = async () => { const result = await getListUser() setListUsers(result ?? []) } fetchData() }, []) const getListProfilesData = async (filterSearch: Filter) => { try { const params = { userID: filterSearch.userID ?? '', fromDate: filterSearch.fromDate ? moment(filterSearch.fromDate).format('YYYY-MM-DD') : null, toDate: filterSearch.toDate ? moment(filterSearch.toDate).format('YYYY-MM-DD') : null, } const res = await get(sprintReview, params) if (res.status) { return res.data } } catch (error: any) { notifications.show({ title: 'Error', message: error.message ?? error, color: 'red', }) } return [] } const getListTechnicalByUserId = async (id: string) => { try { const params = {} const res = await get(`${getAllTechByUserId}/${id}`, params) if (res.status) { return res.data } } catch (error: any) { notifications.show({ title: 'Error', message: error.message ?? error, color: 'red', }) } return [] } const getListProjectReview = async (filterSearch: Filter) => { try { const params = { userID: filterSearch.userID ?? '', fromDate: filterSearch.fromDate ? moment(filterSearch.fromDate).format('YYYY-MM-DD') : null, toDate: filterSearch.toDate ? moment(filterSearch.toDate).format('YYYY-MM-DD') : null, } const res = await get(projectReview, params) if (res.status) { return res.data } } catch (error: any) { notifications.show({ title: 'Error', message: error.message ?? error, color: 'red', }) } return [] } const getListSummaryTracking = async (filterSearch: Filter) => { try { const params = { userID: filterSearch.userID ?? '', fromDate: filterSearch.fromDate ? moment(filterSearch.fromDate).format('YYYY-MM-DD') : null, toDate: filterSearch.toDate ? moment(filterSearch.toDate).format('YYYY-MM-DD') : null, } const res = await get(getListTrackingSummary, params) if (res.status) { return res.data } } catch (error: any) { notifications.show({ title: 'Error', message: error.message ?? error, color: 'red', }) } return [] } const getListProjectParticipating = async (filterSearch: Filter) => { try { const fromDate = filterSearch.fromDate ? moment(filterSearch.fromDate).format('YYYY-MM-DD') : moment(new Date()).format('YYYY-MM-DD') const toDate = filterSearch.toDate ? moment(filterSearch.toDate).format('YYYY-MM-DD') : moment(new Date()).format('YYYY-MM-DD') const params = { userID: filterSearch.userID ?? '', fromDate: fromDate, toDate: toDate, } const res = await get(getPJParticipating, params) if (res.status) { const value = processJiraData(res.data, fromDate, toDate, res.accountId) return value } } catch (error: any) { notifications.show({ title: 'Error', message: error.message ?? error, color: 'red', }) } return [] } function processJiraData( data: any, startDate: any, endDate: any, accountId: string, ) { const projectSummary: any = {} const start = new Date(startDate) const end = new Date(endDate) data.issues.forEach((issue: any) => { const projectName = issue.fields.project.name const worklogs = issue.fields.worklog.worklogs // Filter worklogs based on 'started' date range const filteredWorklogs = worklogs.filter((log: any) => { const logDate = new Date(log.started) return ( logDate >= start && logDate <= end && accountId === log?.updateAuthor?.accountId ) }) if (filteredWorklogs.length === 0) return // Skip if no worklogs in range if (!projectSummary[projectName]) { projectSummary[projectName] = { project_name: projectName, total_task: 0, total_time_spent: 0, } } // Get unique issueIds within the filtered worklogs const uniqueIssues = new Set( filteredWorklogs.map((log: any) => log.issueId), ) // Sum up total time spent from filtered worklogs const totalTimeSpent = filteredWorklogs.reduce( (sum: number, log: any) => sum + log.timeSpentSeconds, 0, ) projectSummary[projectName].total_task += uniqueIssues.size projectSummary[projectName].total_time_spent += totalTimeSpent }) const returnValue: DataPJParticipating[] = Object.values(projectSummary) return returnValue } useEffect(() => { if (filter?.userID) { setLoading(true) setLoadingReview(true) setLoadingWorkingStyle(true) const fetchData = async () => { const result = await getListProfilesData(filter) setDataProfile(result ?? []) setLoading(false) } const fetchDataProject = async () => { const result = await getListProfilesData(filter) const resultProject = await getListProjectReview(filter) setDataProfile(result ?? []) setDataProjectReview(resultProject ?? []) setLoadingReview(false) } const fetchDataTracking = async () => { const resultTracking = await getListSummaryTracking(filter) setDataSummaryTracking(resultTracking ?? []) setLoadingWorkingStyle(false) } const fetchDataPJParticipating = async () => { const resultPJParticipating = await getListProjectParticipating(filter) setDataPJParticipating(resultPJParticipating ?? []) setLoadingPJParticipating(false) } fetchData() fetchDataProject() fetchDataTracking() if (filter?.fromDate && filter?.toDate) { setLoadingPJParticipating(true) fetchDataPJParticipating() } } }, [filter]) useEffect(() => { if (filter?.userID) { setLoadingTechnical(true) const fetchData = async () => { const result = await getListTechnicalByUserId(filter?.userID) setDataTechnical(result ?? []) setLoadingTechnical(false) } fetchData() } }, [filter?.userID]) const columns = [ { name: 'level', size: '10%', header: 'Level', render: (row: any) => { return ( {row?.level ? row.level : ''} ) }, }, { name: 'name', size: '', header: 'Name', }, { name: 'point', size: '10%', header: 'Point', render: (row: any) => { if (row?.point > 0) return (
{row?.point}
) }, }, { name: 'updated_at', size: '25%', header: 'Last update', render: (row: any) => { if (row?.updated_at) return (
{moment(row?.updated_at).format('DD/MM/YYYY HH:mm:ss')}
) }, }, ] const infoTotal = () => { // Tính tổng point và số lượng point > 0 let totalPoint = 0 let count = 0 dataTechnical.forEach((item) => { if (item.point > 0) { totalPoint += item.point count++ } }) const averagePoint = count > 0 ? (totalPoint / count).toFixed(2) : '0.00' return (
Avg: {averagePoint} Total: {totalPoint}
) } const columnsProjectReview = [ // { // name: 'id', // size: '5%', // header: 'Num', // render: (row: any) => { // return ( // // {row?.id ? row.id : ''} // // ) // }, // }, { name: 'name', size: '15%', header: 'Name', }, { name: 'role', size: '15%', header: 'Role', }, { name: 'note', size: '', header: 'Note', }, { name: 'created_at', size: '10%', header: 'Created at', render: (row: any) => { if (row?.created_at) return (
{moment(row?.created_at).format('DD/MM/YYYY HH:mm:ss')}
) }, }, { name: 'updated_at', size: '10%', header: 'Last update', render: (row: any) => { if (row?.updated_at) return (
{moment(row?.updated_at).format('DD/MM/YYYY HH:mm:ss')}
) }, }, { name: '#', size: '5%', header: 'Action', render: (row: DataProjectReview) => { return ( { setAction('edit') form.setValues(row) }} width={20} height={20} /> { setAction('delete') setItem(row) }} width={20} height={20} /> ) }, }, ] const columnsPJParticipating = [ { name: 'project_name', size: '50%', header: 'Name', }, { name: 'total_task', size: '25%', header: 'Total task', }, { name: 'total_time_spent', size: '25%', header: 'Total time spent', render: (row: any) => { return
{row?.total_time_spent / 60 / 60}h
}, }, ] const columnsDetailWorking = [ { name: 'name', size: '40%', header: 'Name', }, { name: 'time_string', size: '40%', header: 'Time', render: (row: any) => { return moment(row.time_string).format('YYYY/MM/DD - HH:mm:ss') }, }, { name: 'status', size: '20%', header: 'Status', }, ] const handleCreate = async (values: DataProjectReview) => { try { const res = await post(projectReviewAdd, { name: values.name, role: values.role, note: values.note, user_id: filter.userID, }) if (res.id) { setAction('') form.reset() const resultProject = await getListProjectReview(filter) setDataProjectReview(resultProject ?? []) } } catch (error) { console.log(error) } } const handleUpdate = async (values: DataProjectReview) => { try { const res = await update(projectReviewUpdate, { id: values.id, name: values.name, role: values.role, note: values.note, user_id: filter.userID, }) if (res) { setAction('') form.reset() const resultProject = await getListProjectReview(filter) setDataProjectReview(resultProject ?? []) } } catch (error) { console.log(error) } } const handleDelete = async (id: number) => { try { await Xdelete(projectReviewDelete, { id: id }, async () => { const resultProject = await getListProjectReview(filter) setDataProjectReview(resultProject ?? []) }) } catch (error) { console.log(error) } } return (

Admin/ Staff Evaluation

{loadingExportAll ? ( ) : ( )}
User: