Update Working review #105
			
				
			
		
		
		
	| 
						 | 
				
			
			@ -337,4 +337,20 @@ class JiraController extends Controller
 | 
			
		|||
            Mail::to([$email])->cc(['admin@apactech.io', 'joseph@apactech.io'])->send(new WarningLongTask($user_info[$email]));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getUserProjectParticipating(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        $userID = $request->input('userID');
 | 
			
		||||
        $startDate = $request->input('fromDate');
 | 
			
		||||
        $endDate = $request->input('toDate');
 | 
			
		||||
        $user = User::find($userID);
 | 
			
		||||
        $userJira = $this->jiraService->getUserByEmail($user->email);
 | 
			
		||||
        $projects = $this->jiraService->getUserWorkLogs($userJira[0]['accountId'], $startDate, $endDate);
 | 
			
		||||
 | 
			
		||||
        return response()->json([
 | 
			
		||||
            'data' => $projects,
 | 
			
		||||
            "accountId" => $userJira[0]['accountId'],
 | 
			
		||||
            'status' => true
 | 
			
		||||
        ], 200);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -214,8 +214,7 @@ class TrackingController extends Controller
 | 
			
		|||
            $afternoon_condition_time = Carbon::createFromTimeString('01:10PM')->setTimezone(env('TIME_ZONE'));
 | 
			
		||||
            $admin_mails = Admin::where('permission', 'like', '%admin%')->pluck('email');
 | 
			
		||||
 | 
			
		||||
            if ($time->greaterThan($morning_condition_time) && $time->lessThan($afternoon_time))
 | 
			
		||||
            {
 | 
			
		||||
            if ($time->greaterThan($morning_condition_time) && $time->lessThan($afternoon_time)) {
 | 
			
		||||
                $period = 'morning';
 | 
			
		||||
                $minutes_late = $morning_time->diffInMinutes($time);
 | 
			
		||||
                $data = array(
 | 
			
		||||
| 
						 | 
				
			
			@ -250,4 +249,78 @@ class TrackingController extends Controller
 | 
			
		|||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getSummaryTracking(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        $request->validate([
 | 
			
		||||
            'userID' => 'required|exists:users,id',
 | 
			
		||||
            'fromDate' => 'nullable|date',
 | 
			
		||||
            'toDate' => 'nullable|date',
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        $userID = $request->input('userID');
 | 
			
		||||
        $startDate = $request->input('fromDate');
 | 
			
		||||
        $endDate = $request->input('toDate');
 | 
			
		||||
 | 
			
		||||
        $trackingData = Tracking::where('user_id', $userID)
 | 
			
		||||
            ->where('status', 'check in');
 | 
			
		||||
 | 
			
		||||
        if ($startDate && $endDate) {
 | 
			
		||||
            $trackingData->whereBetween('updated_at', [$startDate, $endDate . ' 23:59:59']);
 | 
			
		||||
        } elseif ($startDate) {
 | 
			
		||||
            $trackingData->where('updated_at', '>=', $startDate);
 | 
			
		||||
        } elseif ($endDate) {
 | 
			
		||||
            $trackingData->where('updated_at', '<=', $endDate . ' 23:59:59');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $onTimeMorning = 0;
 | 
			
		||||
        $lateMorning = 0;
 | 
			
		||||
        $onTimeAfternoon = 0;
 | 
			
		||||
        $lateAfternoon = 0;
 | 
			
		||||
        $datesChecked = [];
 | 
			
		||||
 | 
			
		||||
        $trackingData->get()->groupBy(function ($record) {
 | 
			
		||||
            return Carbon::parse($record->time_string)->toDateString();
 | 
			
		||||
        })->each(function ($records, $date) use (&$onTimeMorning, &$lateMorning, &$onTimeAfternoon, &$lateAfternoon, &$datesChecked) {
 | 
			
		||||
            $morningCheck = $records->filter(function ($record) {
 | 
			
		||||
                return Carbon::parse($record->time_string)->hour < 12;
 | 
			
		||||
            })->sortBy('time_string')->first();
 | 
			
		||||
 | 
			
		||||
            $afternoonCheck = $records->filter(function ($record) {
 | 
			
		||||
                return Carbon::parse($record->time_string)->hour >= 12;
 | 
			
		||||
            })->sortBy('time_string')->first();
 | 
			
		||||
 | 
			
		||||
            $morningTime = Carbon::parse($date)->setTime(7, 40, 0);
 | 
			
		||||
            $afternoonTime = Carbon::parse($date)->setTime(13, 10, 0);
 | 
			
		||||
 | 
			
		||||
            if ($morningCheck) {
 | 
			
		||||
                $checkInTime = Carbon::parse($morningCheck->time_string);
 | 
			
		||||
                if ($checkInTime->lessThanOrEqualTo($morningTime)) {
 | 
			
		||||
                    $onTimeMorning++;
 | 
			
		||||
                } else {
 | 
			
		||||
                    $lateMorning++;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if ($afternoonCheck) {
 | 
			
		||||
                $checkInTime = Carbon::parse($afternoonCheck->time_string);
 | 
			
		||||
                if ($checkInTime->lessThanOrEqualTo($afternoonTime)) {
 | 
			
		||||
                    $onTimeAfternoon++;
 | 
			
		||||
                } else {
 | 
			
		||||
                    $lateAfternoon++;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // Default to on time if no afternoon check-in
 | 
			
		||||
                $onTimeAfternoon++;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return AbstractController::ResultSuccess([
 | 
			
		||||
            'on_time_morning' => $onTimeMorning,
 | 
			
		||||
            'late_morning' => $lateMorning,
 | 
			
		||||
            'on_time_afternoon' => $onTimeAfternoon,
 | 
			
		||||
            'late_afternoon' => $lateAfternoon,
 | 
			
		||||
            'value' => $trackingData->get()
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -116,6 +116,7 @@ Route::middleware('api')
 | 
			
		|||
                Route::get('/worklogs', [JiraController::class, 'getAllUserWorkLogs'])->middleware('check.permission:admin.staff');
 | 
			
		||||
                Route::get('/allocation', [JiraController::class, 'getAllUserDoing'])->middleware('check.permission:admin.staff');
 | 
			
		||||
                Route::get('/issue/detail', [JiraController::class, 'getDetailIssueById'])->middleware('check.permission:admin.staff');
 | 
			
		||||
                Route::get('/project-participating', [JiraController::class, 'getUserProjectParticipating'])->middleware('check.permission:admin');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            Route::group([
 | 
			
		||||
| 
						 | 
				
			
			@ -136,6 +137,7 @@ Route::middleware('api')
 | 
			
		|||
                Route::post('/create', [TrackingController::class, 'create'])->middleware('check.permission:admin.hr');
 | 
			
		||||
                Route::post('/update', [TrackingController::class, 'update'])->middleware('check.permission:admin.hr');
 | 
			
		||||
                Route::get('/delete', [TrackingController::class, 'delete'])->middleware('check.permission:admin.hr');
 | 
			
		||||
                Route::get('/summary', [TrackingController::class, 'getSummaryTracking'])->middleware('check.permission:admin');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            Route::group([
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -12,6 +12,7 @@
 | 
			
		|||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@codemirror/lang-javascript": "^6.2.2",
 | 
			
		||||
    "@mantine/charts": "^7.16.3",
 | 
			
		||||
    "@mantine/core": "^7.13.2",
 | 
			
		||||
    "@mantine/dates": "^7.13.2",
 | 
			
		||||
    "@mantine/form": "^7.13.2",
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +37,7 @@
 | 
			
		|||
    "react-redux": "^8.1.3",
 | 
			
		||||
    "react-router-dom": "^6.19.0",
 | 
			
		||||
    "reactstrap": "^9.2.2",
 | 
			
		||||
    "recharts": "^2.11.0",
 | 
			
		||||
    "recharts": "^2.15.1",
 | 
			
		||||
    "tailwind-merge": "^2.0.0",
 | 
			
		||||
    "tests": "^0.4.2"
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,7 @@ import { BrowserRouter, useRoutes } from 'react-router-dom'
 | 
			
		|||
import mainRoutes from '@/routes/main'
 | 
			
		||||
import classes from '@/App.module.css'
 | 
			
		||||
import '@mantine/dates/styles.css'
 | 
			
		||||
import '@mantine/charts/styles.css'
 | 
			
		||||
 | 
			
		||||
export const App = () => {
 | 
			
		||||
  const element = useRoutes(mainRoutes)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@ export const getListTracking = API_URL + 'v1/admin/tracking'
 | 
			
		|||
export const addTracking = API_URL + 'v1/admin/tracking/create'
 | 
			
		||||
export const updateTracking = API_URL + 'v1/admin/tracking/update'
 | 
			
		||||
export const deleteTracking = API_URL + 'v1/admin/tracking/delete'
 | 
			
		||||
export const getListTrackingSummary = API_URL + 'v1/admin/tracking/summary'
 | 
			
		||||
 | 
			
		||||
// Worklogs
 | 
			
		||||
export const fetchAllIssues = API_URL + 'v1/admin/jira/fetch-issues'
 | 
			
		||||
| 
						 | 
				
			
			@ -17,6 +18,8 @@ export const getAllIssuesByProject =
 | 
			
		|||
export const getAllUserWorklogs = API_URL + 'v1/admin/jira/worklogs'
 | 
			
		||||
export const getAllUserDoing = API_URL + 'v1/admin/jira/allocation'
 | 
			
		||||
export const getDetailIssByKey = API_URL + 'v1/admin/jira/issue/detail'
 | 
			
		||||
export const getPJParticipating =
 | 
			
		||||
  API_URL + 'v1/admin/jira/project-participating'
 | 
			
		||||
 | 
			
		||||
//Timekeeping
 | 
			
		||||
export const getTheTimesheet = API_URL + 'v1/admin/timekeeping'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -200,3 +200,46 @@
 | 
			
		|||
  width: rem(20px);
 | 
			
		||||
  height: rem(22px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chartContainer {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  margin-top: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.boxContainer {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pjParticipatingContainer {
 | 
			
		||||
  margin-top: 20px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.boxColorLime {
 | 
			
		||||
  width: 12px;
 | 
			
		||||
  height: 12px;
 | 
			
		||||
  background-color: #82c91e;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.boxColorRed {
 | 
			
		||||
  width: 12px;
 | 
			
		||||
  height: 12px;
 | 
			
		||||
  background-color: #fa5252;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.boxColorOrange {
 | 
			
		||||
  width: 12px;
 | 
			
		||||
  height: 12px;
 | 
			
		||||
  background-color: #fd7e14;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.boxColorTeal {
 | 
			
		||||
  width: 12px;
 | 
			
		||||
  height: 12px;
 | 
			
		||||
  background-color: #12b886;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,8 @@ import {
 | 
			
		|||
  projectReviewUpdate,
 | 
			
		||||
  projectReviewDelete,
 | 
			
		||||
  evaluationReportAllUsers,
 | 
			
		||||
  getListTrackingSummary,
 | 
			
		||||
  getPJParticipating,
 | 
			
		||||
} from '@/api/Admin'
 | 
			
		||||
import DataTableAll from '@/components/DataTable/DataTable'
 | 
			
		||||
import ProjectInvolvement from '@/components/ProjectInvolvement/ProjectInvolvement'
 | 
			
		||||
| 
						 | 
				
			
			@ -35,10 +37,12 @@ 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
 | 
			
		||||
| 
						 | 
				
			
			@ -76,8 +80,32 @@ interface DataProjectReview {
 | 
			
		|||
  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<any>([])
 | 
			
		||||
  const [dataTechnical, setDataTechnical] = useState<DataTechnical[]>([])
 | 
			
		||||
| 
						 | 
				
			
			@ -96,6 +124,17 @@ const StaffEvaluation = () => {
 | 
			
		|||
  const [activeBtn, setActiveBtn] = useState(false)
 | 
			
		||||
  const [loadingExport, setLoadingExport] = useState(false)
 | 
			
		||||
  const [loadingExportAll, setLoadingExportAll] = useState(false)
 | 
			
		||||
  const [dataPJParticipating, setDataPJParticipating] = useState<
 | 
			
		||||
    DataPJParticipating[]
 | 
			
		||||
  >([])
 | 
			
		||||
  const [dataSummaryTracking, setDataSummaryTracking] =
 | 
			
		||||
    useState<DataSummaryTracking>({
 | 
			
		||||
      on_time_morning: 0,
 | 
			
		||||
      late_morning: 0,
 | 
			
		||||
      on_time_afternoon: 0,
 | 
			
		||||
      late_afternoon: 0,
 | 
			
		||||
      value: [],
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
  const form = useForm({
 | 
			
		||||
    initialValues: {
 | 
			
		||||
| 
						 | 
				
			
			@ -298,17 +337,147 @@ const StaffEvaluation = () => {
 | 
			
		|||
    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 ?? [])
 | 
			
		||||
        setLoading(false)
 | 
			
		||||
        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])
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -433,7 +602,7 @@ const StaffEvaluation = () => {
 | 
			
		|||
    {
 | 
			
		||||
      name: 'name',
 | 
			
		||||
      size: '15%',
 | 
			
		||||
      header: 'Project Name',
 | 
			
		||||
      header: 'Name',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'role',
 | 
			
		||||
| 
						 | 
				
			
			@ -512,6 +681,27 @@ const StaffEvaluation = () => {
 | 
			
		|||
    },
 | 
			
		||||
  ]
 | 
			
		||||
 | 
			
		||||
  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 <div>{row?.total_time_spent / 60 / 60}h</div>
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  ]
 | 
			
		||||
 | 
			
		||||
  const handleCreate = async (values: DataProjectReview) => {
 | 
			
		||||
    try {
 | 
			
		||||
      const res = await post(projectReviewAdd, {
 | 
			
		||||
| 
						 | 
				
			
			@ -717,6 +907,12 @@ const StaffEvaluation = () => {
 | 
			
		|||
          >
 | 
			
		||||
            <span style={{ fontSize: '16px' }}>Project review</span>
 | 
			
		||||
          </Tabs.Tab>
 | 
			
		||||
          <Tabs.Tab
 | 
			
		||||
            value="working_style"
 | 
			
		||||
            leftSection={<IconReportAnalytics size={16} color="#fab005" />}
 | 
			
		||||
          >
 | 
			
		||||
            <span style={{ fontSize: '16px' }}>Working review</span>
 | 
			
		||||
          </Tabs.Tab>
 | 
			
		||||
        </Tabs.List>
 | 
			
		||||
 | 
			
		||||
        <Tabs.Panel value="general">
 | 
			
		||||
| 
						 | 
				
			
			@ -785,9 +981,12 @@ const StaffEvaluation = () => {
 | 
			
		|||
 | 
			
		||||
        <Tabs.Panel value="project_review">
 | 
			
		||||
          <Box className={classes.userInfoSection} display="flex">
 | 
			
		||||
            {loading ? (
 | 
			
		||||
            {loadingReview ? (
 | 
			
		||||
              <Box
 | 
			
		||||
                style={{ width: '100%', display: loading ? 'block' : 'none' }}
 | 
			
		||||
                style={{
 | 
			
		||||
                  width: '100%',
 | 
			
		||||
                  display: loadingReview ? 'block' : 'none',
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
                <Box
 | 
			
		||||
                  style={{
 | 
			
		||||
| 
						 | 
				
			
			@ -832,6 +1031,129 @@ const StaffEvaluation = () => {
 | 
			
		|||
            )}
 | 
			
		||||
          </Box>
 | 
			
		||||
        </Tabs.Panel>
 | 
			
		||||
        <Tabs.Panel value="working_style">
 | 
			
		||||
          {loadingWorkingStyle ? (
 | 
			
		||||
            <Box
 | 
			
		||||
              style={{
 | 
			
		||||
                width: '100%',
 | 
			
		||||
                display: loadingWorkingStyle ? 'block' : 'none',
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              <Box
 | 
			
		||||
                style={{
 | 
			
		||||
                  marginTop: '10%',
 | 
			
		||||
                  textAlign: 'center',
 | 
			
		||||
                  // display: 'none',
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
                <Loader size={'sm'} color="green" type="bars" m={'0 auto'} />
 | 
			
		||||
                <Text fw={600} c={'gray'}>
 | 
			
		||||
                  Loading . . .
 | 
			
		||||
                </Text>
 | 
			
		||||
              </Box>
 | 
			
		||||
            </Box>
 | 
			
		||||
          ) : (
 | 
			
		||||
            <Box>
 | 
			
		||||
              <Box className={classes.chartContainer} display="flex">
 | 
			
		||||
                <PieChart
 | 
			
		||||
                  withLabelsLine
 | 
			
		||||
                  labelsPosition="outside"
 | 
			
		||||
                  labelsType="value"
 | 
			
		||||
                  withLabels={dataSummaryTracking.value.length > 0}
 | 
			
		||||
                  withTooltip
 | 
			
		||||
                  data={
 | 
			
		||||
                    dataSummaryTracking.value.length > 0
 | 
			
		||||
                      ? [
 | 
			
		||||
                          {
 | 
			
		||||
                            name: 'On time morning',
 | 
			
		||||
                            value: dataSummaryTracking?.on_time_morning ?? 0,
 | 
			
		||||
                            color: 'lime',
 | 
			
		||||
                          },
 | 
			
		||||
                          {
 | 
			
		||||
                            name: 'Late morning',
 | 
			
		||||
                            value: dataSummaryTracking?.late_morning ?? 0,
 | 
			
		||||
                            color: 'red',
 | 
			
		||||
                          },
 | 
			
		||||
                          {
 | 
			
		||||
                            name: 'On time afternoon',
 | 
			
		||||
                            value: dataSummaryTracking?.on_time_afternoon ?? 0,
 | 
			
		||||
                            color: 'teal',
 | 
			
		||||
                          },
 | 
			
		||||
                          {
 | 
			
		||||
                            name: 'Late afternoon',
 | 
			
		||||
                            value: dataSummaryTracking?.late_afternoon ?? 0,
 | 
			
		||||
                            color: 'orange',
 | 
			
		||||
                          },
 | 
			
		||||
                        ]
 | 
			
		||||
                      : [
 | 
			
		||||
                          {
 | 
			
		||||
                            name: 'No data',
 | 
			
		||||
                            value: 1,
 | 
			
		||||
                            color: 'gray.6',
 | 
			
		||||
                          },
 | 
			
		||||
                        ]
 | 
			
		||||
                  }
 | 
			
		||||
                />
 | 
			
		||||
              </Box>
 | 
			
		||||
              <Box className={classes.boxContainer} display="flex">
 | 
			
		||||
                <Box className={classes.boxContainer} display="flex">
 | 
			
		||||
                  <div className={classes.boxColorLime}></div>
 | 
			
		||||
                  <div style={{ paddingLeft: '10px', paddingRight: '20px' }}>
 | 
			
		||||
                    On time morning
 | 
			
		||||
                  </div>
 | 
			
		||||
                </Box>
 | 
			
		||||
                <Box className={classes.boxContainer} display="flex">
 | 
			
		||||
                  <div className={classes.boxColorRed}></div>
 | 
			
		||||
                  <div style={{ paddingLeft: '10px', paddingRight: '20px' }}>
 | 
			
		||||
                    Late morning
 | 
			
		||||
                  </div>
 | 
			
		||||
                </Box>
 | 
			
		||||
                <Box className={classes.boxContainer} display="flex">
 | 
			
		||||
                  <div className={classes.boxColorTeal}></div>
 | 
			
		||||
                  <div style={{ paddingLeft: '10px', paddingRight: '20px' }}>
 | 
			
		||||
                    On time afternoon
 | 
			
		||||
                  </div>
 | 
			
		||||
                </Box>
 | 
			
		||||
                <Box className={classes.boxContainer} display="flex">
 | 
			
		||||
                  <div className={classes.boxColorOrange}></div>
 | 
			
		||||
                  <div style={{ paddingLeft: '10px' }}>Late afternoon</div>
 | 
			
		||||
                </Box>
 | 
			
		||||
              </Box>
 | 
			
		||||
            </Box>
 | 
			
		||||
          )}
 | 
			
		||||
          <Box className={classes.pjParticipatingContainer}>
 | 
			
		||||
            <Title order={4}>Project Participating</Title>
 | 
			
		||||
          </Box>
 | 
			
		||||
          <Box className={classes.boxContainer} display="flex">
 | 
			
		||||
            {loadingPJParticipating ? (
 | 
			
		||||
              <Box
 | 
			
		||||
                style={{
 | 
			
		||||
                  width: '100%',
 | 
			
		||||
                  display: loadingPJParticipating ? 'block' : 'none',
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
                <Box
 | 
			
		||||
                  style={{
 | 
			
		||||
                    marginTop: '10%',
 | 
			
		||||
                    textAlign: 'center',
 | 
			
		||||
                    // display: 'none',
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  <Loader size={'sm'} color="green" type="bars" m={'0 auto'} />
 | 
			
		||||
                  <Text fw={600} c={'gray'}>
 | 
			
		||||
                    Analyzing . . .
 | 
			
		||||
                  </Text>
 | 
			
		||||
                </Box>
 | 
			
		||||
              </Box>
 | 
			
		||||
            ) : (
 | 
			
		||||
              <DataTableAll
 | 
			
		||||
                data={dataPJParticipating}
 | 
			
		||||
                columns={columnsPJParticipating}
 | 
			
		||||
                size=""
 | 
			
		||||
              />
 | 
			
		||||
            )}
 | 
			
		||||
          </Box>
 | 
			
		||||
        </Tabs.Panel>
 | 
			
		||||
      </Tabs>
 | 
			
		||||
 | 
			
		||||
      {/* Add/Edit User modal */}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue