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
|
||||||
{
|
{
|
||||||
|
|
@ -24,7 +25,7 @@ class TrackingController extends Controller
|
||||||
use HasFilterRequest;
|
use HasFilterRequest;
|
||||||
use HasSearchRequest;
|
use HasSearchRequest;
|
||||||
use AnalyzeData;
|
use AnalyzeData;
|
||||||
|
|
||||||
private $CHECK_IN = 'check in';
|
private $CHECK_IN = 'check in';
|
||||||
private $CHECK_OUT = 'check out';
|
private $CHECK_OUT = 'check out';
|
||||||
|
|
||||||
|
|
@ -207,15 +208,14 @@ class TrackingController extends Controller
|
||||||
// $status = $this->CHECK_IN;
|
// $status = $this->CHECK_IN;
|
||||||
// $lastCheck =Tracking::find(1)->created_at;
|
// $lastCheck =Tracking::find(1)->created_at;
|
||||||
// $user = Admin::where('name', 'LE TAN LUAN')->first();
|
// $user = Admin::where('name', 'LE TAN LUAN')->first();
|
||||||
if($status === $this->CHECK_IN){
|
if ($status === $this->CHECK_IN) {
|
||||||
$morning_time = Carbon::createFromTimeString('07:30AM')->setTimezone(env('TIME_ZONE'));
|
$morning_time = Carbon::createFromTimeString('07:30AM')->setTimezone(env('TIME_ZONE'));
|
||||||
$morning_condition_time = Carbon::createFromTimeString('07:40AM')->setTimezone(env('TIME_ZONE'));
|
$morning_condition_time = Carbon::createFromTimeString('07:40AM')->setTimezone(env('TIME_ZONE'));
|
||||||
$afternoon_time = Carbon::createFromTimeString('11:30AM')->setTimezone(env('TIME_ZONE'));
|
$afternoon_time = Carbon::createFromTimeString('11:30AM')->setTimezone(env('TIME_ZONE'));
|
||||||
$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(
|
||||||
|
|
@ -223,9 +223,9 @@ class TrackingController extends Controller
|
||||||
"email" => $user->email,
|
"email" => $user->email,
|
||||||
"name" => $user->name,
|
"name" => $user->name,
|
||||||
"admin_mails" => $admin_mails,
|
"admin_mails" => $admin_mails,
|
||||||
"message1" => "Your ". $period ." starts ". $minutes_late ." minutes late",
|
"message1" => "Your " . $period . " starts " . $minutes_late . " minutes late",
|
||||||
"message2" => "You checked in at [" . $time ."]",
|
"message2" => "You checked in at [" . $time . "]",
|
||||||
"url" => env('ADMIN_URL')."/tracking?search=&per_page=10&page=1&timezone=Asia%2FSaigon&name=".$user->name."&time_string=".$time->format("Y-m-d H:i")."&status=check+in",
|
"url" => env('ADMIN_URL') . "/tracking?search=&per_page=10&page=1&timezone=Asia%2FSaigon&name=" . $user->name . "&time_string=" . $time->format("Y-m-d H:i") . "&status=check+in",
|
||||||
"subject" => "[Management System] Late warning - " . $user->name
|
"subject" => "[Management System] Late warning - " . $user->name
|
||||||
);
|
);
|
||||||
Mail::to($user->email)->cc($admin_mails)->send(new CheckinLateMail($data));
|
Mail::to($user->email)->cc($admin_mails)->send(new CheckinLateMail($data));
|
||||||
|
|
@ -247,7 +247,117 @@ class TrackingController extends Controller
|
||||||
// Mail::to($user->email)->cc($admin_mails)->send(new CheckinLateMail($data));
|
// Mail::to($user->email)->cc($admin_mails)->send(new CheckinLateMail($data));
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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([
|
||||||
|
|
|
||||||
|
|
@ -231,12 +231,12 @@ class JiraService
|
||||||
$users_data = [];
|
$users_data = [];
|
||||||
$user_warning = [];
|
$user_warning = [];
|
||||||
foreach ($users as $user) {
|
foreach ($users as $user) {
|
||||||
$user = (array)$user[0];
|
$user = (array) $user[0];
|
||||||
$users_data[$user['displayName']]['user'] = $user;
|
$users_data[$user['displayName']]['user'] = $user;
|
||||||
$users_data[$user['displayName']]['total_spent'] = 0;
|
$users_data[$user['displayName']]['total_spent'] = 0;
|
||||||
$users_data[$user['displayName']]['total_est'] = 0;
|
$users_data[$user['displayName']]['total_est'] = 0;
|
||||||
$body = [
|
$body = [
|
||||||
'expand' => ['names', 'schema','changelog'],
|
'expand' => ['names', 'schema', 'changelog'],
|
||||||
'fields' => ['summary', 'status', 'timeoriginalestimate', 'timespent', 'assignee', 'project', 'updated'],
|
'fields' => ['summary', 'status', 'timeoriginalestimate', 'timespent', 'assignee', 'project', 'updated'],
|
||||||
'jql' => sprintf(
|
'jql' => sprintf(
|
||||||
"assignee = '%s' AND status IN ('to do', 'todo', 'in progress') ORDER BY updated DESC",
|
"assignee = '%s' AND status IN ('to do', 'todo', 'in progress') ORDER BY updated DESC",
|
||||||
|
|
|
||||||
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,43 +377,45 @@ export const DataTableAll = ({
|
||||||
</Box>
|
</Box>
|
||||||
{componentRight}
|
{componentRight}
|
||||||
</Box>
|
</Box>
|
||||||
<Box className={classes.box}>
|
<Box className={classes.box} style={{ height: height + 50 }}>
|
||||||
<Table
|
<ScrollArea h={height}>
|
||||||
stickyHeader
|
<Table
|
||||||
stickyHeaderOffset={-1}
|
stickyHeader
|
||||||
striped
|
stickyHeaderOffset={-1}
|
||||||
highlightOnHover
|
striped
|
||||||
withTableBorder
|
highlightOnHover
|
||||||
withColumnBorders
|
withTableBorder
|
||||||
>
|
withColumnBorders
|
||||||
<Table.Thead className={classes.headers}>
|
>
|
||||||
<Table.Tr>
|
<Table.Thead className={classes.headers}>
|
||||||
<Table.Th display={checkBox ? 'block' : 'none'}>
|
<Table.Tr>
|
||||||
<Checkbox
|
<Table.Th display={checkBox ? 'block' : 'none'}>
|
||||||
aria-label="Select row"
|
<Checkbox
|
||||||
checked={
|
aria-label="Select row"
|
||||||
checkSubArray(Tdata, selectedRows) &&
|
checked={
|
||||||
Tdata.length === selectedRows.length
|
checkSubArray(Tdata, selectedRows) &&
|
||||||
}
|
Tdata.length === selectedRows.length
|
||||||
onChange={(event) =>
|
}
|
||||||
setSelectedRows(
|
onChange={(event) =>
|
||||||
event.currentTarget.checked
|
setSelectedRows(
|
||||||
? (pre) => [...pre, ...Tdata]
|
event.currentTarget.checked
|
||||||
: selectedRows.filter(
|
? (pre) => [...pre, ...Tdata]
|
||||||
(item) =>
|
: selectedRows.filter(
|
||||||
!Tdata.some((removeItem) =>
|
(item) =>
|
||||||
areObjectsEqual(item, removeItem),
|
!Tdata.some((removeItem) =>
|
||||||
),
|
areObjectsEqual(item, removeItem),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
}
|
)
|
||||||
/>
|
}
|
||||||
</Table.Th>
|
/>
|
||||||
{headers}
|
</Table.Th>
|
||||||
</Table.Tr>
|
{headers}
|
||||||
</Table.Thead>
|
</Table.Tr>
|
||||||
<Table.Tbody>{rows}</Table.Tbody>
|
</Table.Thead>
|
||||||
</Table>
|
<Table.Tbody>{rows}</Table.Tbody>
|
||||||
|
</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) {
|
||||||
|
|
@ -174,7 +177,7 @@ export const exportFile = async (url: string, params: any = {}, fileName: string
|
||||||
const link = document.createElement('a')
|
const link = document.createElement('a')
|
||||||
link.href = downloadUrl
|
link.href = downloadUrl
|
||||||
link.download = fileName
|
link.download = fileName
|
||||||
|
|
||||||
document.body.appendChild(link)
|
document.body.appendChild(link)
|
||||||
link.click()
|
link.click()
|
||||||
window.URL.revokeObjectURL(downloadUrl)
|
window.URL.revokeObjectURL(downloadUrl)
|
||||||
|
|
@ -188,7 +191,7 @@ export const exportFile = async (url: string, params: any = {}, fileName: string
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('Export failed')
|
throw new Error('Export failed')
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue