Merge branch 'master' into dev
This commit is contained in:
		
						commit
						1018172b58
					
				| 
						 | 
					@ -337,4 +337,20 @@ class JiraController extends Controller
 | 
				
			||||||
            Mail::to([$email])->cc(['admin@apactech.io', 'joseph@apactech.io'])->send(new WarningLongTask($user_info[$email]));
 | 
					            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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,7 @@ use Illuminate\Support\Facades\Storage;
 | 
				
			||||||
use Modules\Admin\app\Models\Admin;
 | 
					use Modules\Admin\app\Models\Admin;
 | 
				
			||||||
use Modules\Admin\app\Models\MonthlyTimekeeping;
 | 
					use Modules\Admin\app\Models\MonthlyTimekeeping;
 | 
				
			||||||
use Modules\Admin\app\Models\Tracking;
 | 
					use Modules\Admin\app\Models\Tracking;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\DB;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TrackingController extends Controller
 | 
					class TrackingController extends Controller
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					@ -214,8 +215,7 @@ class TrackingController extends Controller
 | 
				
			||||||
            $afternoon_condition_time = Carbon::createFromTimeString('01:10PM')->setTimezone(env('TIME_ZONE'));
 | 
					            $afternoon_condition_time = Carbon::createFromTimeString('01:10PM')->setTimezone(env('TIME_ZONE'));
 | 
				
			||||||
            $admin_mails = Admin::where('permission', 'like', '%admin%')->pluck('email');
 | 
					            $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';
 | 
					                $period = 'morning';
 | 
				
			||||||
                $minutes_late = $morning_time->diffInMinutes($time);
 | 
					                $minutes_late = $morning_time->diffInMinutes($time);
 | 
				
			||||||
                $data = array(
 | 
					                $data = array(
 | 
				
			||||||
| 
						 | 
					@ -250,4 +250,114 @@ 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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($startDate && $endDate) {
 | 
				
			||||||
 | 
					            $trackingData->whereBetween(
 | 
				
			||||||
 | 
					                DB::raw("STR_TO_DATE(time_string, '%Y-%m-%d %H:%i:%s')"),
 | 
				
			||||||
 | 
					                [$startDate, $endDate . ' 23:59:59']
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        } elseif ($startDate) {
 | 
				
			||||||
 | 
					            $trackingData->where(
 | 
				
			||||||
 | 
					                DB::raw("STR_TO_DATE(time_string, '%Y-%m-%d %H:%i:%s')"),
 | 
				
			||||||
 | 
					                '>=',
 | 
				
			||||||
 | 
					                $startDate
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        } elseif ($endDate) {
 | 
				
			||||||
 | 
					            $trackingData->where(
 | 
				
			||||||
 | 
					                DB::raw("STR_TO_DATE(time_string, '%Y-%m-%d %H:%i:%s')"),
 | 
				
			||||||
 | 
					                '<=',
 | 
				
			||||||
 | 
					                $endDate . ' 23:59:59'
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $onTimeMorning = 0;
 | 
				
			||||||
 | 
					        $lateMorning = 0;
 | 
				
			||||||
 | 
					        $onTimeAfternoon = 0;
 | 
				
			||||||
 | 
					        $lateAfternoon = 0;
 | 
				
			||||||
 | 
					        $returnTracking = $trackingData->get();
 | 
				
			||||||
 | 
					        $listLate = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $returnTracking->groupBy(function ($record) {
 | 
				
			||||||
 | 
					            return Carbon::parse($record->time_string)->toDateString();
 | 
				
			||||||
 | 
					        })->each(function ($records, $date) use ($userID, &$listLate, &$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) {
 | 
				
			||||||
 | 
					                $time = Carbon::parse($record->time_string)->hour;
 | 
				
			||||||
 | 
					                return $time >= 12 && $time <= 14;
 | 
				
			||||||
 | 
					            })->sortBy('time_string')->first();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $morningTime = Carbon::parse($date)->setTime(7, 40, 0);
 | 
				
			||||||
 | 
					            $afternoonTime = Carbon::parse($date)->setTime(13, 10, 0);
 | 
				
			||||||
 | 
					            $checkOutAfternoonTime = Carbon::parse($date)->setTime(15, 00, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ($morningCheck) {
 | 
				
			||||||
 | 
					                $checkInTime = Carbon::parse($morningCheck->time_string);
 | 
				
			||||||
 | 
					                if ($checkInTime->lessThanOrEqualTo($morningTime)) {
 | 
				
			||||||
 | 
					                    $onTimeMorning++;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    array_push($listLate, $morningCheck->id);
 | 
				
			||||||
 | 
					                    $lateMorning++;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ($afternoonCheck) {
 | 
				
			||||||
 | 
					                $checkInTime = Carbon::parse($afternoonCheck->time_string);
 | 
				
			||||||
 | 
					                if ($checkInTime->lessThanOrEqualTo($afternoonTime)) {
 | 
				
			||||||
 | 
					                    $onTimeAfternoon++;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    array_push($listLate, $afternoonCheck->id);
 | 
				
			||||||
 | 
					                    $lateAfternoon++;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // print ($date . "\n");
 | 
				
			||||||
 | 
					                $checkOutAfternoon = Tracking::where('user_id', $userID)->whereBetween(
 | 
				
			||||||
 | 
					                    DB::raw("STR_TO_DATE(time_string, '%Y-%m-%d %H:%i:%s')"),
 | 
				
			||||||
 | 
					                    [$date, $date . ' 23:59:59'],
 | 
				
			||||||
 | 
					                )->where("status", "check out")->first();
 | 
				
			||||||
 | 
					                // check if not check-in afternoon but has check out
 | 
				
			||||||
 | 
					                if ($checkOutAfternoon) {
 | 
				
			||||||
 | 
					                    $timeCheck = Carbon::parse($checkOutAfternoon->time_string);
 | 
				
			||||||
 | 
					                    // print ($timeCheck . "\n");
 | 
				
			||||||
 | 
					                    if ($checkOutAfternoon && $timeCheck->greaterThanOrEqualTo($checkOutAfternoonTime)) {
 | 
				
			||||||
 | 
					                        $onTimeAfternoon++;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // ** Add `isLate` flag to each record **
 | 
				
			||||||
 | 
					        foreach ($returnTracking as $record) {
 | 
				
			||||||
 | 
					            if (in_array($record->id, $listLate)) {
 | 
				
			||||||
 | 
					                $record->isLate = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return AbstractController::ResultSuccess([
 | 
				
			||||||
 | 
					            'on_time_morning' => $onTimeMorning,
 | 
				
			||||||
 | 
					            'late_morning' => $lateMorning,
 | 
				
			||||||
 | 
					            'on_time_afternoon' => $onTimeAfternoon,
 | 
				
			||||||
 | 
					            'late_afternoon' => $lateAfternoon,
 | 
				
			||||||
 | 
					            'value' => $returnTracking
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -116,6 +116,7 @@ Route::middleware('api')
 | 
				
			||||||
                Route::get('/worklogs', [JiraController::class, 'getAllUserWorkLogs'])->middleware('check.permission:admin.staff');
 | 
					                Route::get('/worklogs', [JiraController::class, 'getAllUserWorkLogs'])->middleware('check.permission:admin.staff');
 | 
				
			||||||
                Route::get('/allocation', [JiraController::class, 'getAllUserDoing'])->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('/issue/detail', [JiraController::class, 'getDetailIssueById'])->middleware('check.permission:admin.staff');
 | 
				
			||||||
 | 
					                Route::get('/project-participating', [JiraController::class, 'getUserProjectParticipating'])->middleware('check.permission:admin');
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Route::group([
 | 
					            Route::group([
 | 
				
			||||||
| 
						 | 
					@ -136,6 +137,7 @@ Route::middleware('api')
 | 
				
			||||||
                Route::post('/create', [TrackingController::class, 'create'])->middleware('check.permission:admin.hr');
 | 
					                Route::post('/create', [TrackingController::class, 'create'])->middleware('check.permission:admin.hr');
 | 
				
			||||||
                Route::post('/update', [TrackingController::class, 'update'])->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('/delete', [TrackingController::class, 'delete'])->middleware('check.permission:admin.hr');
 | 
				
			||||||
 | 
					                Route::get('/summary', [TrackingController::class, 'getSummaryTracking'])->middleware('check.permission:admin');
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Route::group([
 | 
					            Route::group([
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
					@ -12,6 +12,7 @@
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@codemirror/lang-javascript": "^6.2.2",
 | 
					    "@codemirror/lang-javascript": "^6.2.2",
 | 
				
			||||||
 | 
					    "@mantine/charts": "^7.16.3",
 | 
				
			||||||
    "@mantine/core": "^7.13.2",
 | 
					    "@mantine/core": "^7.13.2",
 | 
				
			||||||
    "@mantine/dates": "^7.13.2",
 | 
					    "@mantine/dates": "^7.13.2",
 | 
				
			||||||
    "@mantine/form": "^7.13.2",
 | 
					    "@mantine/form": "^7.13.2",
 | 
				
			||||||
| 
						 | 
					@ -36,7 +37,7 @@
 | 
				
			||||||
    "react-redux": "^8.1.3",
 | 
					    "react-redux": "^8.1.3",
 | 
				
			||||||
    "react-router-dom": "^6.19.0",
 | 
					    "react-router-dom": "^6.19.0",
 | 
				
			||||||
    "reactstrap": "^9.2.2",
 | 
					    "reactstrap": "^9.2.2",
 | 
				
			||||||
    "recharts": "^2.11.0",
 | 
					    "recharts": "^2.15.1",
 | 
				
			||||||
    "tailwind-merge": "^2.0.0",
 | 
					    "tailwind-merge": "^2.0.0",
 | 
				
			||||||
    "tests": "^0.4.2"
 | 
					    "tests": "^0.4.2"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@ import { BrowserRouter, useRoutes } from 'react-router-dom'
 | 
				
			||||||
import mainRoutes from '@/routes/main'
 | 
					import mainRoutes from '@/routes/main'
 | 
				
			||||||
import classes from '@/App.module.css'
 | 
					import classes from '@/App.module.css'
 | 
				
			||||||
import '@mantine/dates/styles.css'
 | 
					import '@mantine/dates/styles.css'
 | 
				
			||||||
 | 
					import '@mantine/charts/styles.css'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const App = () => {
 | 
					export const App = () => {
 | 
				
			||||||
  const element = useRoutes(mainRoutes)
 | 
					  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 addTracking = API_URL + 'v1/admin/tracking/create'
 | 
				
			||||||
export const updateTracking = API_URL + 'v1/admin/tracking/update'
 | 
					export const updateTracking = API_URL + 'v1/admin/tracking/update'
 | 
				
			||||||
export const deleteTracking = API_URL + 'v1/admin/tracking/delete'
 | 
					export const deleteTracking = API_URL + 'v1/admin/tracking/delete'
 | 
				
			||||||
 | 
					export const getListTrackingSummary = API_URL + 'v1/admin/tracking/summary'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Worklogs
 | 
					// Worklogs
 | 
				
			||||||
export const fetchAllIssues = API_URL + 'v1/admin/jira/fetch-issues'
 | 
					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 getAllUserWorklogs = API_URL + 'v1/admin/jira/worklogs'
 | 
				
			||||||
export const getAllUserDoing = API_URL + 'v1/admin/jira/allocation'
 | 
					export const getAllUserDoing = API_URL + 'v1/admin/jira/allocation'
 | 
				
			||||||
export const getDetailIssByKey = API_URL + 'v1/admin/jira/issue/detail'
 | 
					export const getDetailIssByKey = API_URL + 'v1/admin/jira/issue/detail'
 | 
				
			||||||
 | 
					export const getPJParticipating =
 | 
				
			||||||
 | 
					  API_URL + 'v1/admin/jira/project-participating'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//Timekeeping
 | 
					//Timekeeping
 | 
				
			||||||
export const getTheTimesheet = API_URL + 'v1/admin/timekeeping'
 | 
					export const getTheTimesheet = API_URL + 'v1/admin/timekeeping'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@ import {
 | 
				
			||||||
  MultiSelect,
 | 
					  MultiSelect,
 | 
				
			||||||
  Pagination,
 | 
					  Pagination,
 | 
				
			||||||
  RadioGroup,
 | 
					  RadioGroup,
 | 
				
			||||||
 | 
					  ScrollArea,
 | 
				
			||||||
  Select,
 | 
					  Select,
 | 
				
			||||||
  Skeleton,
 | 
					  Skeleton,
 | 
				
			||||||
  Table,
 | 
					  Table,
 | 
				
			||||||
| 
						 | 
					@ -85,6 +86,8 @@ export const DataTableAll = ({
 | 
				
			||||||
  size,
 | 
					  size,
 | 
				
			||||||
  infoTotal,
 | 
					  infoTotal,
 | 
				
			||||||
  componentRight,
 | 
					  componentRight,
 | 
				
			||||||
 | 
					  height = 600,
 | 
				
			||||||
 | 
					  keyHighlight = '',
 | 
				
			||||||
}: {
 | 
					}: {
 | 
				
			||||||
  data: any[]
 | 
					  data: any[]
 | 
				
			||||||
  columns: Column[]
 | 
					  columns: Column[]
 | 
				
			||||||
| 
						 | 
					@ -94,6 +97,8 @@ export const DataTableAll = ({
 | 
				
			||||||
  size: string
 | 
					  size: string
 | 
				
			||||||
  infoTotal?: React.ReactNode // Set the type to ReactNode to allow JSX elements
 | 
					  infoTotal?: React.ReactNode // Set the type to ReactNode to allow JSX elements
 | 
				
			||||||
  componentRight?: React.ReactNode
 | 
					  componentRight?: React.ReactNode
 | 
				
			||||||
 | 
					  height?: number
 | 
				
			||||||
 | 
					  keyHighlight?: string
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
  const [Tdata, setTData] = useState<any[]>(data)
 | 
					  const [Tdata, setTData] = useState<any[]>(data)
 | 
				
			||||||
  // const [tempData, setTempData] = useState<any[]>([])
 | 
					  // const [tempData, setTempData] = useState<any[]>([])
 | 
				
			||||||
| 
						 | 
					@ -206,6 +211,9 @@ export const DataTableAll = ({
 | 
				
			||||||
            ? 'var(--mantine-color-blue-light)'
 | 
					            ? 'var(--mantine-color-blue-light)'
 | 
				
			||||||
            : undefined
 | 
					            : undefined
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        style={{
 | 
				
			||||||
 | 
					          backgroundColor: element[keyHighlight] ? '#ff70704d' : 'transparent',
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <Table.Td display={checkBox ? 'block' : 'none'}>
 | 
					        <Table.Td display={checkBox ? 'block' : 'none'}>
 | 
				
			||||||
          <Checkbox
 | 
					          <Checkbox
 | 
				
			||||||
| 
						 | 
					@ -369,7 +377,8 @@ export const DataTableAll = ({
 | 
				
			||||||
        </Box>
 | 
					        </Box>
 | 
				
			||||||
        {componentRight}
 | 
					        {componentRight}
 | 
				
			||||||
      </Box>
 | 
					      </Box>
 | 
				
			||||||
      <Box className={classes.box}>
 | 
					      <Box className={classes.box} style={{ height: height + 50 }}>
 | 
				
			||||||
 | 
					        <ScrollArea h={height}>
 | 
				
			||||||
          <Table
 | 
					          <Table
 | 
				
			||||||
            stickyHeader
 | 
					            stickyHeader
 | 
				
			||||||
            stickyHeaderOffset={-1}
 | 
					            stickyHeaderOffset={-1}
 | 
				
			||||||
| 
						 | 
					@ -406,6 +415,7 @@ export const DataTableAll = ({
 | 
				
			||||||
            </Table.Thead>
 | 
					            </Table.Thead>
 | 
				
			||||||
            <Table.Tbody>{rows}</Table.Tbody>
 | 
					            <Table.Tbody>{rows}</Table.Tbody>
 | 
				
			||||||
          </Table>
 | 
					          </Table>
 | 
				
			||||||
 | 
					        </ScrollArea>
 | 
				
			||||||
      </Box>
 | 
					      </Box>
 | 
				
			||||||
    </Container>
 | 
					    </Container>
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -55,8 +55,6 @@
 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
  margin-top: 20px;
 | 
					  margin-top: 20px;
 | 
				
			||||||
  gap: 10px;
 | 
					  gap: 10px;
 | 
				
			||||||
  max-height: 72vh;
 | 
					 | 
				
			||||||
  overflow-y: scroll;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.titleSidebar {
 | 
					.titleSidebar {
 | 
				
			||||||
| 
						 | 
					@ -202,3 +200,46 @@
 | 
				
			||||||
  width: rem(20px);
 | 
					  width: rem(20px);
 | 
				
			||||||
  height: rem(22px);
 | 
					  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,
 | 
					  projectReviewUpdate,
 | 
				
			||||||
  projectReviewDelete,
 | 
					  projectReviewDelete,
 | 
				
			||||||
  evaluationReportAllUsers,
 | 
					  evaluationReportAllUsers,
 | 
				
			||||||
 | 
					  getListTrackingSummary,
 | 
				
			||||||
 | 
					  getPJParticipating,
 | 
				
			||||||
} from '@/api/Admin'
 | 
					} from '@/api/Admin'
 | 
				
			||||||
import DataTableAll from '@/components/DataTable/DataTable'
 | 
					import DataTableAll from '@/components/DataTable/DataTable'
 | 
				
			||||||
import ProjectInvolvement from '@/components/ProjectInvolvement/ProjectInvolvement'
 | 
					import ProjectInvolvement from '@/components/ProjectInvolvement/ProjectInvolvement'
 | 
				
			||||||
| 
						 | 
					@ -35,10 +37,12 @@ import {
 | 
				
			||||||
  IconClearAll,
 | 
					  IconClearAll,
 | 
				
			||||||
  IconEdit,
 | 
					  IconEdit,
 | 
				
			||||||
  IconPresentationAnalytics,
 | 
					  IconPresentationAnalytics,
 | 
				
			||||||
 | 
					  IconReportAnalytics,
 | 
				
			||||||
  IconX,
 | 
					  IconX,
 | 
				
			||||||
} from '@tabler/icons-react'
 | 
					} from '@tabler/icons-react'
 | 
				
			||||||
import { useForm } from '@mantine/form'
 | 
					import { useForm } from '@mantine/form'
 | 
				
			||||||
import { update, Xdelete } from '@/rtk/helpers/CRUD'
 | 
					import { update, Xdelete } from '@/rtk/helpers/CRUD'
 | 
				
			||||||
 | 
					import { PieChart } from '@mantine/charts'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface User {
 | 
					interface User {
 | 
				
			||||||
  id: number
 | 
					  id: number
 | 
				
			||||||
| 
						 | 
					@ -76,8 +80,32 @@ interface DataProjectReview {
 | 
				
			||||||
  updated_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 StaffEvaluation = () => {
 | 
				
			||||||
  const [loading, setLoading] = useState(false)
 | 
					  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 [loadingTechnical, setLoadingTechnical] = useState(false)
 | 
				
			||||||
  const [dataProfile, setDataProfile] = useState<any>([])
 | 
					  const [dataProfile, setDataProfile] = useState<any>([])
 | 
				
			||||||
  const [dataTechnical, setDataTechnical] = useState<DataTechnical[]>([])
 | 
					  const [dataTechnical, setDataTechnical] = useState<DataTechnical[]>([])
 | 
				
			||||||
| 
						 | 
					@ -96,6 +124,17 @@ const StaffEvaluation = () => {
 | 
				
			||||||
  const [activeBtn, setActiveBtn] = useState(false)
 | 
					  const [activeBtn, setActiveBtn] = useState(false)
 | 
				
			||||||
  const [loadingExport, setLoadingExport] = useState(false)
 | 
					  const [loadingExport, setLoadingExport] = useState(false)
 | 
				
			||||||
  const [loadingExportAll, setLoadingExportAll] = 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({
 | 
					  const form = useForm({
 | 
				
			||||||
    initialValues: {
 | 
					    initialValues: {
 | 
				
			||||||
| 
						 | 
					@ -103,6 +142,9 @@ const StaffEvaluation = () => {
 | 
				
			||||||
      name: '',
 | 
					      name: '',
 | 
				
			||||||
      role: '',
 | 
					      role: '',
 | 
				
			||||||
      note: '',
 | 
					      note: '',
 | 
				
			||||||
 | 
					      user_id: 0,
 | 
				
			||||||
 | 
					      created_at: '',
 | 
				
			||||||
 | 
					      updated_at: '',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    validate: {
 | 
					    validate: {
 | 
				
			||||||
      name: (value) =>
 | 
					      name: (value) =>
 | 
				
			||||||
| 
						 | 
					@ -295,17 +337,147 @@ const StaffEvaluation = () => {
 | 
				
			||||||
    return []
 | 
					    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(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (filter?.userID) {
 | 
					    if (filter?.userID) {
 | 
				
			||||||
      setLoading(true)
 | 
					      setLoading(true)
 | 
				
			||||||
 | 
					      setLoadingReview(true)
 | 
				
			||||||
 | 
					      setLoadingWorkingStyle(true)
 | 
				
			||||||
      const fetchData = async () => {
 | 
					      const fetchData = async () => {
 | 
				
			||||||
 | 
					        const result = await getListProfilesData(filter)
 | 
				
			||||||
 | 
					        setDataProfile(result ?? [])
 | 
				
			||||||
 | 
					        setLoading(false)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      const fetchDataProject = async () => {
 | 
				
			||||||
        const result = await getListProfilesData(filter)
 | 
					        const result = await getListProfilesData(filter)
 | 
				
			||||||
        const resultProject = await getListProjectReview(filter)
 | 
					        const resultProject = await getListProjectReview(filter)
 | 
				
			||||||
        setDataProfile(result ?? [])
 | 
					        setDataProfile(result ?? [])
 | 
				
			||||||
        setDataProjectReview(resultProject ?? [])
 | 
					        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()
 | 
					      fetchData()
 | 
				
			||||||
 | 
					      fetchDataProject()
 | 
				
			||||||
 | 
					      fetchDataTracking()
 | 
				
			||||||
 | 
					      if (filter?.fromDate && filter?.toDate) {
 | 
				
			||||||
 | 
					        setLoadingPJParticipating(true)
 | 
				
			||||||
 | 
					        fetchDataPJParticipating()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }, [filter])
 | 
					  }, [filter])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -430,7 +602,7 @@ const StaffEvaluation = () => {
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      name: 'name',
 | 
					      name: 'name',
 | 
				
			||||||
      size: '15%',
 | 
					      size: '15%',
 | 
				
			||||||
      header: 'Project Name',
 | 
					      header: 'Name',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      name: 'role',
 | 
					      name: 'role',
 | 
				
			||||||
| 
						 | 
					@ -509,11 +681,54 @@ 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 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) => {
 | 
					  const handleCreate = async (values: DataProjectReview) => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const { id, ...data } = values
 | 
					 | 
				
			||||||
      const res = await post(projectReviewAdd, {
 | 
					      const res = await post(projectReviewAdd, {
 | 
				
			||||||
        ...data,
 | 
					        name: values.name,
 | 
				
			||||||
 | 
					        role: values.role,
 | 
				
			||||||
 | 
					        note: values.note,
 | 
				
			||||||
        user_id: filter.userID,
 | 
					        user_id: filter.userID,
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      if (res.id) {
 | 
					      if (res.id) {
 | 
				
			||||||
| 
						 | 
					@ -530,7 +745,10 @@ const StaffEvaluation = () => {
 | 
				
			||||||
  const handleUpdate = async (values: DataProjectReview) => {
 | 
					  const handleUpdate = async (values: DataProjectReview) => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const res = await update(projectReviewUpdate, {
 | 
					      const res = await update(projectReviewUpdate, {
 | 
				
			||||||
        ...values,
 | 
					        id: values.id,
 | 
				
			||||||
 | 
					        name: values.name,
 | 
				
			||||||
 | 
					        role: values.role,
 | 
				
			||||||
 | 
					        note: values.note,
 | 
				
			||||||
        user_id: filter.userID,
 | 
					        user_id: filter.userID,
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      if (res) {
 | 
					      if (res) {
 | 
				
			||||||
| 
						 | 
					@ -710,6 +928,12 @@ const StaffEvaluation = () => {
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            <span style={{ fontSize: '16px' }}>Project review</span>
 | 
					            <span style={{ fontSize: '16px' }}>Project review</span>
 | 
				
			||||||
          </Tabs.Tab>
 | 
					          </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.List>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <Tabs.Panel value="general">
 | 
					        <Tabs.Panel value="general">
 | 
				
			||||||
| 
						 | 
					@ -778,9 +1002,12 @@ const StaffEvaluation = () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <Tabs.Panel value="project_review">
 | 
					        <Tabs.Panel value="project_review">
 | 
				
			||||||
          <Box className={classes.userInfoSection} display="flex">
 | 
					          <Box className={classes.userInfoSection} display="flex">
 | 
				
			||||||
            {loading ? (
 | 
					            {loadingReview ? (
 | 
				
			||||||
              <Box
 | 
					              <Box
 | 
				
			||||||
                style={{ width: '100%', display: loading ? 'block' : 'none' }}
 | 
					                style={{
 | 
				
			||||||
 | 
					                  width: '100%',
 | 
				
			||||||
 | 
					                  display: loadingReview ? 'block' : 'none',
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                <Box
 | 
					                <Box
 | 
				
			||||||
                  style={{
 | 
					                  style={{
 | 
				
			||||||
| 
						 | 
					@ -825,6 +1052,163 @@ const StaffEvaluation = () => {
 | 
				
			||||||
            )}
 | 
					            )}
 | 
				
			||||||
          </Box>
 | 
					          </Box>
 | 
				
			||||||
        </Tabs.Panel>
 | 
					        </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>
 | 
				
			||||||
 | 
					              <Tabs defaultValue="overview" orientation="vertical">
 | 
				
			||||||
 | 
					                <Tabs.List justify="center">
 | 
				
			||||||
 | 
					                  <Tabs.Tab value="overview">Overview</Tabs.Tab>
 | 
				
			||||||
 | 
					                  <Tabs.Tab value="detail">Detail</Tabs.Tab>
 | 
				
			||||||
 | 
					                </Tabs.List>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <Tabs.Panel value="overview">
 | 
				
			||||||
 | 
					                  <Box style={{ height: 350, marginTop: 8 }}>
 | 
				
			||||||
 | 
					                    <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>
 | 
				
			||||||
 | 
					                </Tabs.Panel>
 | 
				
			||||||
 | 
					                <Tabs.Panel value="detail">
 | 
				
			||||||
 | 
					                  <Box style={{ marginTop: 8 }}>
 | 
				
			||||||
 | 
					                    <DataTableAll
 | 
				
			||||||
 | 
					                      data={dataSummaryTracking.value}
 | 
				
			||||||
 | 
					                      columns={columnsDetailWorking}
 | 
				
			||||||
 | 
					                      size=""
 | 
				
			||||||
 | 
					                      height={300}
 | 
				
			||||||
 | 
					                      keyHighlight={'isLate'}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                  </Box>
 | 
				
			||||||
 | 
					                </Tabs.Panel>
 | 
				
			||||||
 | 
					              </Tabs>
 | 
				
			||||||
 | 
					            </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=""
 | 
				
			||||||
 | 
					                height={300}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					          </Box>
 | 
				
			||||||
 | 
					        </Tabs.Panel>
 | 
				
			||||||
      </Tabs>
 | 
					      </Tabs>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {/* Add/Edit User modal */}
 | 
					      {/* Add/Edit User modal */}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ import {
 | 
				
			||||||
  Modal,
 | 
					  Modal,
 | 
				
			||||||
  MultiSelect,
 | 
					  MultiSelect,
 | 
				
			||||||
  Text,
 | 
					  Text,
 | 
				
			||||||
  TextInput
 | 
					  TextInput,
 | 
				
			||||||
} from '@mantine/core'
 | 
					} from '@mantine/core'
 | 
				
			||||||
import { useForm } from '@mantine/form'
 | 
					import { useForm } from '@mantine/form'
 | 
				
			||||||
import { IconEdit, IconX } from '@tabler/icons-react'
 | 
					import { IconEdit, IconX } from '@tabler/icons-react'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,6 @@ import { getFormDataHeader, getHeaderInfo } from '@/rtk/helpers/tokenCreator'
 | 
				
			||||||
import { removeTokens } from '@/rtk/localStorage'
 | 
					import { removeTokens } from '@/rtk/localStorage'
 | 
				
			||||||
import { notifications } from '@mantine/notifications'
 | 
					import { notifications } from '@mantine/notifications'
 | 
				
			||||||
import axios from 'axios'
 | 
					import axios from 'axios'
 | 
				
			||||||
import moment from 'moment'
 | 
					 | 
				
			||||||
const handleResponse = (response: any) => {
 | 
					const handleResponse = (response: any) => {
 | 
				
			||||||
  if (response.status === 401) {
 | 
					  if (response.status === 401) {
 | 
				
			||||||
    removeTokens()
 | 
					    removeTokens()
 | 
				
			||||||
| 
						 | 
					@ -158,14 +157,18 @@ export const postImage = async (url: string, body: any, method: any) => {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const exportFile = async (url: string, params: any = {}, fileName: string) => {
 | 
					export const exportFile = async (
 | 
				
			||||||
 | 
					  url: string,
 | 
				
			||||||
 | 
					  params: any = {},
 | 
				
			||||||
 | 
					  fileName: string,
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
  const header = await getHeaderInfo()
 | 
					  const header = await getHeaderInfo()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const response = await axios.get(url, {
 | 
					    const response = await axios.get(url, {
 | 
				
			||||||
      ...header,
 | 
					      ...header,
 | 
				
			||||||
      params,
 | 
					      params,
 | 
				
			||||||
      responseType: 'blob'
 | 
					      responseType: 'blob',
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (response.status === 200) {
 | 
					    if (response.status === 200) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue