Merge pull request 'master' (#103) from master into dev
Reviewed-on: #103
This commit is contained in:
commit
4d94e26bb9
|
|
@ -8,9 +8,11 @@ use App\Services\JiraService;
|
|||
use Carbon\Carbon;
|
||||
use Modules\Admin\app\Models\TechnicalUser;
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\Admin\app\Models\ProjectReview;
|
||||
use Modules\Admin\app\Models\UserCriteria;
|
||||
use PhpOffice\PhpWord\IOFactory;
|
||||
use PhpOffice\PhpWord\PhpWord;
|
||||
use PhpOffice\PhpWord\SimpleType\Jc;
|
||||
|
||||
class EvaluationController extends Controller
|
||||
{
|
||||
|
|
@ -222,6 +224,50 @@ class EvaluationController extends Controller
|
|||
'spaceAfter' => 600,
|
||||
]);
|
||||
}
|
||||
|
||||
// **ProjectReview Section**
|
||||
// Fetch Project Reviews
|
||||
$projectReviews = ProjectReview::where('user_id', $user->id);
|
||||
if ($startDate && $endDate) {
|
||||
$projectReviews->whereBetween('updated_at', [$startDate, $endDate . ' 23:59:59']);
|
||||
} elseif ($startDate) {
|
||||
$projectReviews->where('updated_at', '>=', $startDate);
|
||||
} elseif ($endDate) {
|
||||
$projectReviews->where('updated_at', '<=', $endDate . ' 23:59:59');
|
||||
}
|
||||
|
||||
if ($projectReviews->get()->count() > 0) {
|
||||
$section->addText("Project Reviews", ['bold' => true, 'size' => 14, 'color' => '000080'], ['alignment' => Jc::CENTER]);
|
||||
|
||||
$table = $section->addTable([
|
||||
'borderColor' => '000000',
|
||||
'borderSize' => 6,
|
||||
'cellMargin' => 80,
|
||||
]);
|
||||
|
||||
// Table Header
|
||||
$table->addRow();
|
||||
$table->addCell(3500)->addText('Project Name', ['bold' => true]);
|
||||
$table->addCell(2500)->addText('Role', ['bold' => true]);
|
||||
$table->addCell(5000)->addText('Note', ['bold' => true]);
|
||||
$table->addCell(2500)->addText('Created At', ['bold' => true]);
|
||||
$table->addCell(2500)->addText('Updated At', ['bold' => true]);
|
||||
|
||||
|
||||
|
||||
foreach ($projectReviews->get() as $review) {
|
||||
$table->addRow();
|
||||
$table->addCell(3500)->addText($review->name);
|
||||
$table->addCell(2500)->addText($review->role);
|
||||
$table->addCell(5000)->addText($review->note);
|
||||
$table->addCell(2500)->addText(Carbon::parse($review->created_at)->format('d/m/Y H:i:s'));
|
||||
$table->addCell(2500)->addText(Carbon::parse($review->updated_at)->format('d/m/Y H:i:s'));
|
||||
}
|
||||
|
||||
$section->addText(' ', [], [
|
||||
'spaceAfter' => 600,
|
||||
]);
|
||||
}
|
||||
if ($technicalData)
|
||||
$section->addPageBreak();
|
||||
//Technical
|
||||
|
|
@ -260,4 +306,156 @@ class EvaluationController extends Controller
|
|||
|
||||
return response()->download($tempFile, "$user->name.docx")->deleteFileAfterSend(true);
|
||||
}
|
||||
|
||||
public function reportAllUsers(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'fromDate' => 'nullable|date',
|
||||
'toDate' => 'nullable|date',
|
||||
]);
|
||||
|
||||
$startDate = $request->input('fromDate');
|
||||
$endDate = $request->input('toDate');
|
||||
|
||||
$users = User::all();
|
||||
|
||||
$phpWord = new PhpWord();
|
||||
$phpWord->setDefaultFontName('Times New Roman');
|
||||
$phpWord->setDefaultFontSize(12);
|
||||
$section = $phpWord->addSection();
|
||||
|
||||
foreach ($users as $index => $user) {
|
||||
$userEmail = $user->email;
|
||||
|
||||
|
||||
// Add user heading
|
||||
$section->addText("Staff Evaluation", ['bold' => true, 'size' => 20, 'color' => '000000'], ['align' => 'center']);
|
||||
|
||||
if ($startDate) {
|
||||
$fromDate = Carbon::parse($startDate)->format('d-m-Y');
|
||||
$section->addText("From: " . $fromDate, ['size' => 12], ['align' => 'end']);
|
||||
}
|
||||
if ($endDate) {
|
||||
$toDate = Carbon::parse($endDate)->format('d-m-Y');
|
||||
$section->addText("To: " . $toDate, ['size' => 12], ['align' => 'end']);
|
||||
}
|
||||
|
||||
$section->addText("{$user->name}", ['bold' => true, 'size' => 14, 'color' => '000000'], ['spaceAfter' => 400]);
|
||||
|
||||
// **Projects Data**
|
||||
$projectsData = self::getProjectReviewByParams($startDate, $endDate, $userEmail);
|
||||
if (!empty($projectsData)) {
|
||||
foreach ($projectsData as $project) {
|
||||
$section->addText("Project: {$project['name']}", ['bold' => true, 'size' => 14, 'color' => '000080']);
|
||||
|
||||
foreach ($project['sprints'] as $sprint) {
|
||||
$section->addText("Sprint: {$sprint['name']}", ['bold' => true, 'italic' => true, 'size' => 12]);
|
||||
|
||||
$table = $section->addTable(['borderSize' => 6, 'cellMargin' => 80]);
|
||||
$table->addRow();
|
||||
$table->addCell(3000)->addText('Criteria', ['bold' => true]);
|
||||
$table->addCell(3000)->addText('Note', ['bold' => true]);
|
||||
$table->addCell(2500)->addText('Created By', ['bold' => true]);
|
||||
$table->addCell(1500)->addText('Point', ['bold' => true]);
|
||||
|
||||
foreach ($sprint['criterias'] as $criteria) {
|
||||
$table->addRow();
|
||||
$table->addCell(3000)->addText($criteria['criteria']);
|
||||
$table->addCell(3000)->addText($criteria['note']);
|
||||
$table->addCell(2500)->addText($criteria['createdBy']);
|
||||
$table->addCell(1500)->addText($criteria['point'] > 0 ? $criteria['point'] : '');
|
||||
}
|
||||
}
|
||||
$section->addText(' ', [], [
|
||||
'spaceAfter' => 600,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// **ProjectReview Section**
|
||||
// Fetch Project Reviews
|
||||
$projectReviews = ProjectReview::where('user_id', $user->id);
|
||||
if ($startDate && $endDate) {
|
||||
$projectReviews->whereBetween('updated_at', [$startDate, $endDate . ' 23:59:59']);
|
||||
} elseif ($startDate) {
|
||||
$projectReviews->where('updated_at', '>=', $startDate);
|
||||
} elseif ($endDate) {
|
||||
$projectReviews->where('updated_at', '<=', $endDate . ' 23:59:59');
|
||||
}
|
||||
if ($projectReviews->get()->count() > 0) {
|
||||
$section->addText("Project Reviews", ['bold' => true, 'size' => 14, 'color' => '000080'], ['alignment' => Jc::CENTER]);
|
||||
|
||||
$table = $section->addTable([
|
||||
'borderColor' => '000000',
|
||||
'borderSize' => 6,
|
||||
'cellMargin' => 80,
|
||||
]);
|
||||
|
||||
// Table Header
|
||||
$table->addRow();
|
||||
$table->addCell(3500)->addText('Project Name', ['bold' => true]);
|
||||
$table->addCell(2500)->addText('Role', ['bold' => true]);
|
||||
$table->addCell(5000)->addText('Note', ['bold' => true]);
|
||||
$table->addCell(2500)->addText('Created At', ['bold' => true]);
|
||||
$table->addCell(2500)->addText('Updated At', ['bold' => true]);
|
||||
|
||||
|
||||
foreach ($projectReviews->get() as $review) {
|
||||
$table->addRow();
|
||||
$table->addCell(3500)->addText($review->name);
|
||||
$table->addCell(2500)->addText($review->role);
|
||||
$table->addCell(5000)->addText($review->note);
|
||||
$table->addCell(2500)->addText(Carbon::parse($review->created_at)->format('d/m/Y H:i:s'));
|
||||
$table->addCell(2500)->addText(Carbon::parse($review->updated_at)->format('d/m/Y H:i:s'));
|
||||
}
|
||||
|
||||
$section->addText(' ', [], [
|
||||
'spaceAfter' => 600,
|
||||
]);
|
||||
}
|
||||
|
||||
// **Technical Section**
|
||||
$section->addText("Technicals", ['bold' => true, 'size' => 14, 'color' => '000080'], ['alignment' => Jc::CENTER]);
|
||||
|
||||
$table = $section->addTable([
|
||||
'borderColor' => '000000',
|
||||
'borderSize' => 6,
|
||||
'cellMargin' => 80,
|
||||
]);
|
||||
|
||||
$table->addRow();
|
||||
$table->addCell(1500)->addText('Level', ['bold' => true]);
|
||||
$table->addCell(3500)->addText('Name', ['bold' => true]);
|
||||
$table->addCell(2500)->addText('Point', ['bold' => true]);
|
||||
$table->addCell(2500)->addText('Last Update', ['bold' => true]);
|
||||
|
||||
// Fetch Technical Data
|
||||
$technicalData = TechnicalController::getDataTechnicalsByUserId($user->id);
|
||||
|
||||
foreach ($technicalData as $technical) {
|
||||
$updated_at = $technical['updated_at'] ? Carbon::parse($technical['updated_at'])->format('d/m/Y H:i:s') : null;
|
||||
$table->addRow();
|
||||
$table->addCell(1500)->addText($technical['level']);
|
||||
$table->addCell(3500)->addText($technical['name']);
|
||||
$table->addCell(2500)->addText($technical['point']);
|
||||
$table->addCell(2500)->addText($updated_at);
|
||||
}
|
||||
|
||||
// Add page break between users (except last one)
|
||||
if ($index < count($users) - 1) {
|
||||
$section->addPageBreak();
|
||||
}
|
||||
}
|
||||
|
||||
// Save & Download Word File
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'word');
|
||||
$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007');
|
||||
$objWriter->save($tempFile);
|
||||
|
||||
return response()->download($tempFile, "All_Users_Report.docx")->deleteFileAfterSend(true);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Admin\app\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Modules\Admin\app\Http\Controllers\AbstractController;
|
||||
use Modules\Admin\app\Models\ProjectReview;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use DateTime;
|
||||
|
||||
class ProjectReviewController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function getListReviews(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');
|
||||
$projectsData = ProjectReview::where('user_id', $userID);
|
||||
|
||||
if ($startDate && $endDate) {
|
||||
$projectsData->whereBetween('updated_at', [$startDate, $endDate . ' 23:59:59']);
|
||||
} elseif ($startDate) {
|
||||
$projectsData->where('updated_at', '>=', $startDate);
|
||||
} elseif ($endDate) {
|
||||
$projectsData->where('updated_at', '<=', $endDate . ' 23:59:59');
|
||||
}
|
||||
return AbstractController::ResultSuccess($projectsData->get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required|string',
|
||||
'role' => 'required|string',
|
||||
'note' => 'required|string',
|
||||
'user_id' => 'required|exists:users,id',
|
||||
]);
|
||||
|
||||
$review = ProjectReview::create($request->all());
|
||||
return response()->json($review, 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'sometimes|required|string',
|
||||
'role' => 'sometimes|required|string',
|
||||
'note' => 'sometimes|required|string',
|
||||
'user_id' => 'sometimes|required|exists:users,id',
|
||||
]);
|
||||
$id = $request->get('id');
|
||||
$projectReview = ProjectReview::find($id);
|
||||
$payload = $request->all();
|
||||
// if ($request->has('created_at')) {
|
||||
// $created_at = Carbon::create($request->get('created_at'))->setTimezone(env('TIME_ZONE'));
|
||||
// $payload['created_at'] = $created_at;
|
||||
// }
|
||||
if ($projectReview) {
|
||||
$projectReview->update($payload);
|
||||
}
|
||||
return response()->json([
|
||||
'data' => $projectReview,
|
||||
'status' => true
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
$id = $request->get('id');
|
||||
ProjectReview::destroy($id);
|
||||
return response()->json(['message' => 'Deleted successfully', 'status' => true]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Admin\app\Http\Controllers;
|
||||
use App\Traits\AnalyzeData;
|
||||
class DataAnalyzer
|
||||
{
|
||||
use AnalyzeData;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Admin\app\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ProjectReview extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['name', 'role', 'note', 'user_id'];
|
||||
|
||||
// Relationship: A review belongs to a user
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ use Modules\Admin\app\Http\Controllers\TimekeepingController;
|
|||
use Modules\Admin\app\Http\Controllers\TrackingController;
|
||||
use Modules\Admin\app\Http\Controllers\CriteriasController;
|
||||
use Modules\Admin\app\Http\Controllers\EvaluationController;
|
||||
use Modules\Admin\app\Http\Controllers\ProjectReviewController;
|
||||
use Modules\Admin\app\Http\Controllers\ProfileController;
|
||||
use Modules\Admin\app\Http\Controllers\TechnicalController;
|
||||
use Modules\Admin\app\Http\Controllers\TestCaseForSprintController;
|
||||
|
|
@ -192,6 +193,11 @@ Route::middleware('api')
|
|||
Route::get('/sprint-review', [EvaluationController::class, 'sprintReview'])->middleware('check.permission:admin');
|
||||
Route::get('/technical', [EvaluationController::class, 'technical'])->middleware('check.permission:admin');
|
||||
Route::get('/report', [EvaluationController::class, 'report'])->middleware('check.permission:admin');
|
||||
Route::get('/report-all-users', [EvaluationController::class, 'reportAllUsers'])->middleware('check.permission:admin');
|
||||
Route::get('/project-review', [ProjectReviewController::class, 'getListReviews'])->middleware('check.permission:admin');
|
||||
Route::post('/project-review/create', [ProjectReviewController::class, 'create'])->middleware('check.permission:admin');
|
||||
Route::post('/project-review/update', [ProjectReviewController::class, 'update'])->middleware('check.permission:admin');
|
||||
Route::get('/project-review/delete', [ProjectReviewController::class, 'destroy'])->middleware('check.permission:admin');
|
||||
});
|
||||
|
||||
Route::group([
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ return [
|
|||
|
|
||||
*/
|
||||
|
||||
'ttl' => env('JWT_TTL', 60*24),
|
||||
'ttl' => env('JWT_TTL', 60*24*365),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('project_reviews', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('role');
|
||||
$table->longText('note');
|
||||
$table->foreignId('user_id')->constrained('users')->onDelete('cascade'); // Khóa ngoại tới bảng users
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('project_reviews');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
dataset
|
||||
test
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import os
|
||||
import shutil
|
||||
|
||||
def organize_files_by_username(folder_path, dest_folder_path):
|
||||
# Lấy danh sách các tệp trong thư mục
|
||||
files = [f for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))]
|
||||
|
||||
for file in files:
|
||||
# Kiểm tra định dạng tên tệp: <username>_checkin_date.png
|
||||
if "_" in file and file.endswith(".png"):
|
||||
username = file.split("_")[0] # Lấy phần username từ tên tệp
|
||||
|
||||
# Tạo đường dẫn thư mục con
|
||||
subfolder_path = os.path.join(folder_path, username)
|
||||
|
||||
# Tạo thư mục con nếu chưa tồn tại
|
||||
if not os.path.exists(subfolder_path):
|
||||
os.makedirs(subfolder_path)
|
||||
|
||||
# Di chuyển tệp vào thư mục con
|
||||
shutil.move(os.path.join(folder_path, file), os.path.join(subfolder_path, file))
|
||||
|
||||
print("Hoàn thành sắp xếp tệp theo username.")
|
||||
|
||||
# Đường dẫn tới thư mục chứa các tệp
|
||||
folder_path = "/home/joseph/screenshot"
|
||||
dest_folder_path = "/home/joseph/DetectFace/dataset"
|
||||
organize_files_by_username(folder_path, dest_folder_path)
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
import cv2
|
||||
import face_recognition
|
||||
import os
|
||||
import numpy as np
|
||||
import pickle
|
||||
datasetPath = "dataset"
|
||||
images = []
|
||||
classNames = []
|
||||
lisFileTrain = os.listdir(datasetPath)
|
||||
|
||||
for file in lisFileTrain:
|
||||
currentImg = cv2.imread(f"{datasetPath}/{file}")
|
||||
images.append(currentImg)
|
||||
classNames.append(os.path.splitext(file)[0].split('_')[0])
|
||||
|
||||
print(len(images))
|
||||
|
||||
def encodeImgs(images, save_path="encodings.pkl"):
|
||||
if os.path.exists(save_path):
|
||||
print(f"Loading encodings from {save_path}...")
|
||||
with open(save_path, "rb") as f:
|
||||
return pickle.load(f)
|
||||
|
||||
encodeList = []
|
||||
for i, img in enumerate(images):
|
||||
print(i+1)
|
||||
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
||||
encode = face_recognition.face_encodings(img)
|
||||
|
||||
if encode: # Check if encodings list is not empty
|
||||
encodeList.append(encode[0])
|
||||
else:
|
||||
print("No face detected in an image. Skipping...")
|
||||
os.remove(f"{datasetPath}/{lisFileTrain[i]}")
|
||||
# Lưu encodeList vào file
|
||||
print(f"Saving encodings to {save_path}...")
|
||||
with open(save_path, "wb") as f:
|
||||
pickle.dump(encodeList, f)
|
||||
|
||||
return encodeList
|
||||
|
||||
encodeListKnow = encodeImgs(images)
|
||||
print("Load data success")
|
||||
print(len(encodeListKnow))
|
||||
|
||||
cap = cv2.VideoCapture(0)
|
||||
|
||||
while True:
|
||||
ret, frame = cap.read()
|
||||
frameS = cv2.resize(frame, (0,0), None, fx=1, fy=1)
|
||||
frameS = cv2.cvtColor(frameS, cv2.COLOR_BGR2RGB)
|
||||
|
||||
faceCurFrame = face_recognition.face_locations(frameS)
|
||||
encodeCurFrame = face_recognition.face_encodings(frameS)
|
||||
|
||||
for encodeFace, faceLoc in zip(encodeCurFrame, faceCurFrame):
|
||||
matches = face_recognition.compare_faces(encodeListKnow, encodeFace)
|
||||
faceDis = face_recognition.face_distance(encodeListKnow, encodeFace)
|
||||
print(faceDis)
|
||||
matchIndex = np.argmin(faceDis)
|
||||
|
||||
if faceDis[matchIndex] < 0.3:
|
||||
name = classNames[matchIndex].upper()
|
||||
else:
|
||||
name = "Unknow"
|
||||
|
||||
y1, x2, y2, x1 = faceLoc
|
||||
y1, x2, y2, x1 = y1, x2, y2, x1
|
||||
cv2.rectangle(frame, (x1,y1), (x2,y2), (0,255,0), 2)
|
||||
cv2.putText(frame, name + f"({(1 - round(faceDis[matchIndex], 2))*100}%)", (x2, y2), cv2.FONT_HERSHEY_COMPLEX, 1, (255,255,255), 2)
|
||||
cv2.imshow('Face decting', frame)
|
||||
|
||||
if cv2.waitKey(1) == ord("q"):
|
||||
break
|
||||
|
||||
cap.release()
|
||||
cv2.destroyAllWindows()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 390 KiB |
|
|
@ -0,0 +1,425 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
{ # this ensures the entire script is downloaded #
|
||||
|
||||
nvm_has() {
|
||||
type "$1" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
nvm_default_install_dir() {
|
||||
[ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm"
|
||||
}
|
||||
|
||||
nvm_install_dir() {
|
||||
if [ -n "$NVM_DIR" ]; then
|
||||
printf %s "${NVM_DIR}"
|
||||
else
|
||||
nvm_default_install_dir
|
||||
fi
|
||||
}
|
||||
|
||||
nvm_latest_version() {
|
||||
echo "v0.35.0"
|
||||
}
|
||||
|
||||
nvm_profile_is_bash_or_zsh() {
|
||||
local TEST_PROFILE
|
||||
TEST_PROFILE="${1-}"
|
||||
case "${TEST_PROFILE-}" in
|
||||
*"/.bashrc" | *"/.bash_profile" | *"/.zshrc")
|
||||
return
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
#
|
||||
# Outputs the location to NVM depending on:
|
||||
# * The availability of $NVM_SOURCE
|
||||
# * The method used ("script" or "git" in the script, defaults to "git")
|
||||
# NVM_SOURCE always takes precedence unless the method is "script-nvm-exec"
|
||||
#
|
||||
nvm_source() {
|
||||
local NVM_METHOD
|
||||
NVM_METHOD="$1"
|
||||
local NVM_SOURCE_URL
|
||||
NVM_SOURCE_URL="$NVM_SOURCE"
|
||||
if [ "_$NVM_METHOD" = "_script-nvm-exec" ]; then
|
||||
NVM_SOURCE_URL="https://raw.githubusercontent.com/nvm-sh/nvm/$(nvm_latest_version)/nvm-exec"
|
||||
elif [ "_$NVM_METHOD" = "_script-nvm-bash-completion" ]; then
|
||||
NVM_SOURCE_URL="https://raw.githubusercontent.com/nvm-sh/nvm/$(nvm_latest_version)/bash_completion"
|
||||
elif [ -z "$NVM_SOURCE_URL" ]; then
|
||||
if [ "_$NVM_METHOD" = "_script" ]; then
|
||||
NVM_SOURCE_URL="https://raw.githubusercontent.com/nvm-sh/nvm/$(nvm_latest_version)/nvm.sh"
|
||||
elif [ "_$NVM_METHOD" = "_git" ] || [ -z "$NVM_METHOD" ]; then
|
||||
NVM_SOURCE_URL="https://github.com/nvm-sh/nvm.git"
|
||||
else
|
||||
echo >&2 "Unexpected value \"$NVM_METHOD\" for \$NVM_METHOD"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
echo "$NVM_SOURCE_URL"
|
||||
}
|
||||
|
||||
#
|
||||
# Node.js version to install
|
||||
#
|
||||
nvm_node_version() {
|
||||
echo "$NODE_VERSION"
|
||||
}
|
||||
|
||||
nvm_download() {
|
||||
if nvm_has "curl"; then
|
||||
curl --compressed -q "$@"
|
||||
elif nvm_has "wget"; then
|
||||
# Emulate curl with wget
|
||||
ARGS=$(echo "$*" | command sed -e 's/--progress-bar /--progress=bar /' \
|
||||
-e 's/-L //' \
|
||||
-e 's/--compressed //' \
|
||||
-e 's/-I /--server-response /' \
|
||||
-e 's/-s /-q /' \
|
||||
-e 's/-o /-O /' \
|
||||
-e 's/-C - /-c /')
|
||||
# shellcheck disable=SC2086
|
||||
eval wget $ARGS
|
||||
fi
|
||||
}
|
||||
|
||||
install_nvm_from_git() {
|
||||
local INSTALL_DIR
|
||||
INSTALL_DIR="$(nvm_install_dir)"
|
||||
|
||||
if [ -d "$INSTALL_DIR/.git" ]; then
|
||||
echo "=> nvm is already installed in $INSTALL_DIR, trying to update using git"
|
||||
command printf '\r=> '
|
||||
command git --git-dir="$INSTALL_DIR"/.git --work-tree="$INSTALL_DIR" fetch origin tag "$(nvm_latest_version)" --depth=1 2> /dev/null || {
|
||||
echo >&2 "Failed to update nvm, run 'git fetch' in $INSTALL_DIR yourself."
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
# Cloning to $INSTALL_DIR
|
||||
echo "=> Downloading nvm from git to '$INSTALL_DIR'"
|
||||
command printf '\r=> '
|
||||
mkdir -p "${INSTALL_DIR}"
|
||||
if [ "$(ls -A "${INSTALL_DIR}")" ]; then
|
||||
command git init "${INSTALL_DIR}" || {
|
||||
echo >&2 'Failed to initialize nvm repo. Please report this!'
|
||||
exit 2
|
||||
}
|
||||
command git --git-dir="${INSTALL_DIR}/.git" remote add origin "$(nvm_source)" 2> /dev/null \
|
||||
|| command git --git-dir="${INSTALL_DIR}/.git" remote set-url origin "$(nvm_source)" || {
|
||||
echo >&2 'Failed to add remote "origin" (or set the URL). Please report this!'
|
||||
exit 2
|
||||
}
|
||||
command git --git-dir="${INSTALL_DIR}/.git" fetch origin tag "$(nvm_latest_version)" --depth=1 || {
|
||||
echo >&2 'Failed to fetch origin with tags. Please report this!'
|
||||
exit 2
|
||||
}
|
||||
else
|
||||
command git -c advice.detachedHead=false clone "$(nvm_source)" -b "$(nvm_latest_version)" --depth=1 "${INSTALL_DIR}" || {
|
||||
echo >&2 'Failed to clone nvm repo. Please report this!'
|
||||
exit 2
|
||||
}
|
||||
fi
|
||||
fi
|
||||
command git -c advice.detachedHead=false --git-dir="$INSTALL_DIR"/.git --work-tree="$INSTALL_DIR" checkout -f --quiet "$(nvm_latest_version)"
|
||||
if [ -n "$(command git --git-dir="$INSTALL_DIR"/.git --work-tree="$INSTALL_DIR" show-ref refs/heads/master)" ]; then
|
||||
if command git --git-dir="$INSTALL_DIR"/.git --work-tree="$INSTALL_DIR" branch --quiet 2>/dev/null; then
|
||||
command git --git-dir="$INSTALL_DIR"/.git --work-tree="$INSTALL_DIR" branch --quiet -D master >/dev/null 2>&1
|
||||
else
|
||||
echo >&2 "Your version of git is out of date. Please update it!"
|
||||
command git --git-dir="$INSTALL_DIR"/.git --work-tree="$INSTALL_DIR" branch -D master >/dev/null 2>&1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "=> Compressing and cleaning up git repository"
|
||||
if ! command git --git-dir="$INSTALL_DIR"/.git --work-tree="$INSTALL_DIR" reflog expire --expire=now --all; then
|
||||
echo >&2 "Your version of git is out of date. Please update it!"
|
||||
fi
|
||||
if ! command git --git-dir="$INSTALL_DIR"/.git --work-tree="$INSTALL_DIR" gc --auto --aggressive --prune=now ; then
|
||||
echo >&2 "Your version of git is out of date. Please update it!"
|
||||
fi
|
||||
return
|
||||
}
|
||||
|
||||
#
|
||||
# Automatically install Node.js
|
||||
#
|
||||
nvm_install_node() {
|
||||
local NODE_VERSION_LOCAL
|
||||
NODE_VERSION_LOCAL="$(nvm_node_version)"
|
||||
|
||||
if [ -z "$NODE_VERSION_LOCAL" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "=> Installing Node.js version $NODE_VERSION_LOCAL"
|
||||
nvm install "$NODE_VERSION_LOCAL"
|
||||
local CURRENT_NVM_NODE
|
||||
|
||||
CURRENT_NVM_NODE="$(nvm_version current)"
|
||||
if [ "$(nvm_version "$NODE_VERSION_LOCAL")" == "$CURRENT_NVM_NODE" ]; then
|
||||
echo "=> Node.js version $NODE_VERSION_LOCAL has been successfully installed"
|
||||
else
|
||||
echo >&2 "Failed to install Node.js $NODE_VERSION_LOCAL"
|
||||
fi
|
||||
}
|
||||
|
||||
install_nvm_as_script() {
|
||||
local INSTALL_DIR
|
||||
INSTALL_DIR="$(nvm_install_dir)"
|
||||
local NVM_SOURCE_LOCAL
|
||||
NVM_SOURCE_LOCAL="$(nvm_source script)"
|
||||
local NVM_EXEC_SOURCE
|
||||
NVM_EXEC_SOURCE="$(nvm_source script-nvm-exec)"
|
||||
local NVM_BASH_COMPLETION_SOURCE
|
||||
NVM_BASH_COMPLETION_SOURCE="$(nvm_source script-nvm-bash-completion)"
|
||||
|
||||
# Downloading to $INSTALL_DIR
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
if [ -f "$INSTALL_DIR/nvm.sh" ]; then
|
||||
echo "=> nvm is already installed in $INSTALL_DIR, trying to update the script"
|
||||
else
|
||||
echo "=> Downloading nvm as script to '$INSTALL_DIR'"
|
||||
fi
|
||||
nvm_download -s "$NVM_SOURCE_LOCAL" -o "$INSTALL_DIR/nvm.sh" || {
|
||||
echo >&2 "Failed to download '$NVM_SOURCE_LOCAL'"
|
||||
return 1
|
||||
} &
|
||||
nvm_download -s "$NVM_EXEC_SOURCE" -o "$INSTALL_DIR/nvm-exec" || {
|
||||
echo >&2 "Failed to download '$NVM_EXEC_SOURCE'"
|
||||
return 2
|
||||
} &
|
||||
nvm_download -s "$NVM_BASH_COMPLETION_SOURCE" -o "$INSTALL_DIR/bash_completion" || {
|
||||
echo >&2 "Failed to download '$NVM_BASH_COMPLETION_SOURCE'"
|
||||
return 2
|
||||
} &
|
||||
for job in $(jobs -p | command sort)
|
||||
do
|
||||
wait "$job" || return $?
|
||||
done
|
||||
chmod a+x "$INSTALL_DIR/nvm-exec" || {
|
||||
echo >&2 "Failed to mark '$INSTALL_DIR/nvm-exec' as executable"
|
||||
return 3
|
||||
}
|
||||
}
|
||||
|
||||
nvm_try_profile() {
|
||||
if [ -z "${1-}" ] || [ ! -f "${1}" ]; then
|
||||
return 1
|
||||
fi
|
||||
echo "${1}"
|
||||
}
|
||||
|
||||
#
|
||||
# Detect profile file if not specified as environment variable
|
||||
# (eg: PROFILE=~/.myprofile)
|
||||
# The echo'ed path is guaranteed to be an existing file
|
||||
# Otherwise, an empty string is returned
|
||||
#
|
||||
nvm_detect_profile() {
|
||||
if [ "${PROFILE-}" = '/dev/null' ]; then
|
||||
# the user has specifically requested NOT to have nvm touch their profile
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -n "${PROFILE}" ] && [ -f "${PROFILE}" ]; then
|
||||
echo "${PROFILE}"
|
||||
return
|
||||
fi
|
||||
|
||||
local DETECTED_PROFILE
|
||||
DETECTED_PROFILE=''
|
||||
|
||||
if [ -n "${BASH_VERSION-}" ]; then
|
||||
if [ -f "$HOME/.bashrc" ]; then
|
||||
DETECTED_PROFILE="$HOME/.bashrc"
|
||||
elif [ -f "$HOME/.bash_profile" ]; then
|
||||
DETECTED_PROFILE="$HOME/.bash_profile"
|
||||
fi
|
||||
elif [ -n "${ZSH_VERSION-}" ]; then
|
||||
DETECTED_PROFILE="$HOME/.zshrc"
|
||||
fi
|
||||
|
||||
if [ -z "$DETECTED_PROFILE" ]; then
|
||||
for EACH_PROFILE in ".profile" ".bashrc" ".bash_profile" ".zshrc"
|
||||
do
|
||||
if DETECTED_PROFILE="$(nvm_try_profile "${HOME}/${EACH_PROFILE}")"; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -n "$DETECTED_PROFILE" ]; then
|
||||
echo "$DETECTED_PROFILE"
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Check whether the user has any globally-installed npm modules in their system
|
||||
# Node, and warn them if so.
|
||||
#
|
||||
nvm_check_global_modules() {
|
||||
command -v npm >/dev/null 2>&1 || return 0
|
||||
|
||||
local NPM_VERSION
|
||||
NPM_VERSION="$(npm --version)"
|
||||
NPM_VERSION="${NPM_VERSION:--1}"
|
||||
[ "${NPM_VERSION%%[!-0-9]*}" -gt 0 ] || return 0
|
||||
|
||||
local NPM_GLOBAL_MODULES
|
||||
NPM_GLOBAL_MODULES="$(
|
||||
npm list -g --depth=0 |
|
||||
command sed -e '/ npm@/d' -e '/ (empty)$/d'
|
||||
)"
|
||||
|
||||
local MODULE_COUNT
|
||||
MODULE_COUNT="$(
|
||||
command printf %s\\n "$NPM_GLOBAL_MODULES" |
|
||||
command sed -ne '1!p' | # Remove the first line
|
||||
wc -l | command tr -d ' ' # Count entries
|
||||
)"
|
||||
|
||||
if [ "${MODULE_COUNT}" != '0' ]; then
|
||||
# shellcheck disable=SC2016
|
||||
echo '=> You currently have modules installed globally with `npm`. These will no'
|
||||
# shellcheck disable=SC2016
|
||||
echo '=> longer be linked to the active version of Node when you install a new node'
|
||||
# shellcheck disable=SC2016
|
||||
echo '=> with `nvm`; and they may (depending on how you construct your `$PATH`)'
|
||||
# shellcheck disable=SC2016
|
||||
echo '=> override the binaries of modules installed with `nvm`:'
|
||||
echo
|
||||
|
||||
command printf %s\\n "$NPM_GLOBAL_MODULES"
|
||||
echo '=> If you wish to uninstall them at a later point (or re-install them under your'
|
||||
# shellcheck disable=SC2016
|
||||
echo '=> `nvm` Nodes), you can remove them from the system Node as follows:'
|
||||
echo
|
||||
echo ' $ nvm use system'
|
||||
echo ' $ npm uninstall -g a_module'
|
||||
echo
|
||||
fi
|
||||
}
|
||||
|
||||
nvm_do_install() {
|
||||
if [ -n "${NVM_DIR-}" ] && ! [ -d "${NVM_DIR}" ]; then
|
||||
if [ -e "${NVM_DIR}" ]; then
|
||||
echo >&2 "File \"${NVM_DIR}\" has the same name as installation directory."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${NVM_DIR}" = "$(nvm_default_install_dir)" ]; then
|
||||
mkdir "${NVM_DIR}"
|
||||
else
|
||||
echo >&2 "You have \$NVM_DIR set to \"${NVM_DIR}\", but that directory does not exist. Check your profile files and environment."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
if [ -z "${METHOD}" ]; then
|
||||
# Autodetect install method
|
||||
if nvm_has git; then
|
||||
install_nvm_from_git
|
||||
elif nvm_has nvm_download; then
|
||||
install_nvm_as_script
|
||||
else
|
||||
echo >&2 'You need git, curl, or wget to install nvm'
|
||||
exit 1
|
||||
fi
|
||||
elif [ "${METHOD}" = 'git' ]; then
|
||||
if ! nvm_has git; then
|
||||
echo >&2 "You need git to install nvm"
|
||||
exit 1
|
||||
fi
|
||||
install_nvm_from_git
|
||||
elif [ "${METHOD}" = 'script' ]; then
|
||||
if ! nvm_has nvm_download; then
|
||||
echo >&2 "You need curl or wget to install nvm"
|
||||
exit 1
|
||||
fi
|
||||
install_nvm_as_script
|
||||
else
|
||||
echo >&2 "The environment variable \$METHOD is set to \"${METHOD}\", which is not recognized as a valid installation method."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
|
||||
local NVM_PROFILE
|
||||
NVM_PROFILE="$(nvm_detect_profile)"
|
||||
local PROFILE_INSTALL_DIR
|
||||
PROFILE_INSTALL_DIR="$(nvm_install_dir | command sed "s:^$HOME:\$HOME:")"
|
||||
|
||||
SOURCE_STR="\\nexport NVM_DIR=\"${PROFILE_INSTALL_DIR}\"\\n[ -s \"\$NVM_DIR/nvm.sh\" ] && \\. \"\$NVM_DIR/nvm.sh\" # This loads nvm\\n"
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
COMPLETION_STR='[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion\n'
|
||||
BASH_OR_ZSH=false
|
||||
|
||||
if [ -z "${NVM_PROFILE-}" ] ; then
|
||||
local TRIED_PROFILE
|
||||
if [ -n "${PROFILE}" ]; then
|
||||
TRIED_PROFILE="${NVM_PROFILE} (as defined in \$PROFILE), "
|
||||
fi
|
||||
echo "=> Profile not found. Tried ${TRIED_PROFILE-}~/.bashrc, ~/.bash_profile, ~/.zshrc, and ~/.profile."
|
||||
echo "=> Create one of them and run this script again"
|
||||
echo " OR"
|
||||
echo "=> Append the following lines to the correct file yourself:"
|
||||
command printf "${SOURCE_STR}"
|
||||
echo
|
||||
else
|
||||
if nvm_profile_is_bash_or_zsh "${NVM_PROFILE-}"; then
|
||||
BASH_OR_ZSH=true
|
||||
fi
|
||||
if ! command grep -qc '/nvm.sh' "$NVM_PROFILE"; then
|
||||
echo "=> Appending nvm source string to $NVM_PROFILE"
|
||||
command printf "${SOURCE_STR}" >> "$NVM_PROFILE"
|
||||
else
|
||||
echo "=> nvm source string already in ${NVM_PROFILE}"
|
||||
fi
|
||||
# shellcheck disable=SC2016
|
||||
if ${BASH_OR_ZSH} && ! command grep -qc '$NVM_DIR/bash_completion' "$NVM_PROFILE"; then
|
||||
echo "=> Appending bash_completion source string to $NVM_PROFILE"
|
||||
command printf "$COMPLETION_STR" >> "$NVM_PROFILE"
|
||||
else
|
||||
echo "=> bash_completion source string already in ${NVM_PROFILE}"
|
||||
fi
|
||||
fi
|
||||
if ${BASH_OR_ZSH} && [ -z "${NVM_PROFILE-}" ] ; then
|
||||
echo "=> Please also append the following lines to the if you are using bash/zsh shell:"
|
||||
command printf "${COMPLETION_STR}"
|
||||
fi
|
||||
|
||||
# Source nvm
|
||||
# shellcheck source=/dev/null
|
||||
\. "$(nvm_install_dir)/nvm.sh"
|
||||
|
||||
nvm_check_global_modules
|
||||
|
||||
nvm_install_node
|
||||
|
||||
nvm_reset
|
||||
|
||||
echo "=> Close and reopen your terminal to start using nvm or run the following to use it now:"
|
||||
command printf "${SOURCE_STR}"
|
||||
if ${BASH_OR_ZSH} ; then
|
||||
command printf "${COMPLETION_STR}"
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Unsets the various functions defined
|
||||
# during the execution of the install script
|
||||
#
|
||||
nvm_reset() {
|
||||
unset -f nvm_has nvm_install_dir nvm_latest_version nvm_profile_is_bash_or_zsh \
|
||||
nvm_source nvm_node_version nvm_download install_nvm_from_git nvm_install_node \
|
||||
install_nvm_as_script nvm_try_profile nvm_detect_profile nvm_check_global_modules \
|
||||
nvm_do_install nvm_reset nvm_default_install_dir
|
||||
}
|
||||
|
||||
[ "_$NVM_ENV" = "_testing" ] || nvm_do_install
|
||||
|
||||
} # this ensures the entire script is downloaded #
|
||||
|
|
@ -37,7 +37,8 @@ export const getListMaster = API_URL + 'v1/admin/category/get-list-master'
|
|||
export const getLeaveManagement = API_URL + 'v1/admin/leave-management'
|
||||
export const updateNoteLeave =
|
||||
API_URL + 'v1/admin/leave-management/saveNoteLeave'
|
||||
export const exportLeaveManagement = API_URL + 'v1/admin/leave-management/export'
|
||||
export const exportLeaveManagement =
|
||||
API_URL + 'v1/admin/leave-management/export'
|
||||
|
||||
//Tickets
|
||||
export const getTickets = API_URL + 'v1/admin/ticket/all'
|
||||
|
|
@ -79,14 +80,24 @@ export const updateProfilesData =
|
|||
API_URL + 'v1/admin/criterias/profiles-data/update'
|
||||
|
||||
export const listUserTechnical = API_URL + 'v1/admin/technical/get-tech-of-user'
|
||||
export const updateUserTechnical = API_URL + 'v1/admin/technical/technicals-user/update'
|
||||
export const updateUserTechnical =
|
||||
API_URL + 'v1/admin/technical/technicals-user/update'
|
||||
|
||||
export const getAllUser = API_URL + 'v1/admin/technical/get-all-user'
|
||||
export const getAllTechByUserId =
|
||||
API_URL + 'v1/admin/technical/get-tech-by-user-id'
|
||||
|
||||
export const evaluation = API_URL + 'v1/admin/evaluation/report'
|
||||
export const evaluationReportAllUsers =
|
||||
API_URL + 'v1/admin/evaluation/report-all-users'
|
||||
export const sprintReview = API_URL + 'v1/admin/evaluation/sprint-review'
|
||||
export const projectReview = API_URL + 'v1/admin/evaluation/project-review'
|
||||
export const projectReviewAdd =
|
||||
API_URL + 'v1/admin/evaluation/project-review/create'
|
||||
export const projectReviewUpdate =
|
||||
API_URL + 'v1/admin/evaluation/project-review/update'
|
||||
export const projectReviewDelete =
|
||||
API_URL + 'v1/admin/evaluation/project-review/delete'
|
||||
|
||||
export const getAllFilesInProfiles = API_URL + 'v1/admin/profile/all-files'
|
||||
export const updateProfileFolder = API_URL + 'v1/admin/profile/update-profile'
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ export const DataTableAll = ({
|
|||
checkBox,
|
||||
size,
|
||||
infoTotal,
|
||||
componentRight,
|
||||
}: {
|
||||
data: any[]
|
||||
columns: Column[]
|
||||
|
|
@ -92,6 +93,7 @@ export const DataTableAll = ({
|
|||
checkBox?: boolean
|
||||
size: string
|
||||
infoTotal?: React.ReactNode // Set the type to ReactNode to allow JSX elements
|
||||
componentRight?: React.ReactNode
|
||||
}) => {
|
||||
const [Tdata, setTData] = useState<any[]>(data)
|
||||
// const [tempData, setTempData] = useState<any[]>([])
|
||||
|
|
@ -325,10 +327,7 @@ export const DataTableAll = ({
|
|||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
className={classes.totalBox}
|
||||
display={infoTotal ? 'flex' : 'none'}
|
||||
>
|
||||
<Box className={classes.totalBox} display={infoTotal ? 'flex' : 'none'}>
|
||||
<Text fz={'sm'} ta={'right'}>
|
||||
{infoTotal}
|
||||
</Text>
|
||||
|
|
@ -368,6 +367,7 @@ export const DataTableAll = ({
|
|||
}}
|
||||
/>
|
||||
</Box>
|
||||
{componentRight}
|
||||
</Box>
|
||||
<Box className={classes.box}>
|
||||
<Table
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@
|
|||
display: flex;
|
||||
margin-top: 20px;
|
||||
gap: 10px;
|
||||
max-height: 72vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.titleSidebar {
|
||||
|
|
|
|||
|
|
@ -2,17 +2,43 @@ import {
|
|||
evaluation,
|
||||
getAllTechByUserId,
|
||||
getAllUser,
|
||||
projectReview,
|
||||
sprintReview,
|
||||
projectReviewAdd,
|
||||
projectReviewUpdate,
|
||||
projectReviewDelete,
|
||||
evaluationReportAllUsers,
|
||||
} from '@/api/Admin'
|
||||
import DataTableAll from '@/components/DataTable/DataTable'
|
||||
import ProjectInvolvement from '@/components/ProjectInvolvement/ProjectInvolvement'
|
||||
import { get, getDownloadFile } from '@/rtk/helpers/apiService'
|
||||
import { Box, Button, Loader, Select, Text, Title } from '@mantine/core'
|
||||
import { get, getDownloadFile, post } from '@/rtk/helpers/apiService'
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Dialog,
|
||||
Group,
|
||||
Loader,
|
||||
Modal,
|
||||
Select,
|
||||
Tabs,
|
||||
Text,
|
||||
Textarea,
|
||||
TextInput,
|
||||
Title,
|
||||
} from '@mantine/core'
|
||||
import { DateInput } from '@mantine/dates'
|
||||
import { notifications } from '@mantine/notifications'
|
||||
import moment from 'moment'
|
||||
import { useEffect, useState } from 'react'
|
||||
import classes from './StaffEvaluation.module.css'
|
||||
import {
|
||||
IconClearAll,
|
||||
IconEdit,
|
||||
IconPresentationAnalytics,
|
||||
IconX,
|
||||
} from '@tabler/icons-react'
|
||||
import { useForm } from '@mantine/form'
|
||||
import { update, Xdelete } from '@/rtk/helpers/CRUD'
|
||||
|
||||
interface User {
|
||||
id: number
|
||||
|
|
@ -40,17 +66,51 @@ interface DataTechnical {
|
|||
updated_at: string
|
||||
}
|
||||
|
||||
interface DataProjectReview {
|
||||
id: number
|
||||
name: string
|
||||
role: string
|
||||
note: string
|
||||
user_id: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
const StaffEvaluation = () => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [loadingTechnical, setLoadingTechnical] = useState(false)
|
||||
const [dataProfile, setDataProfile] = useState<any>([])
|
||||
const [dataTechnical, setDataTechnical] = useState<DataTechnical[]>([])
|
||||
const [dataProjectReview, setDataProjectReview] = useState<
|
||||
DataProjectReview[]
|
||||
>([])
|
||||
const [listUsers, setListUsers] = useState<User[]>([])
|
||||
const [filter, setFilter] = useState<Filter>({
|
||||
userID: '',
|
||||
fromDate: null,
|
||||
toDate: null,
|
||||
})
|
||||
const [action, setAction] = useState('')
|
||||
const [item, setItem] = useState({ id: 0 })
|
||||
const [disableBtn, setDisableBtn] = useState(false)
|
||||
const [activeBtn, setActiveBtn] = useState(false)
|
||||
const [loadingExport, setLoadingExport] = useState(false)
|
||||
const [loadingExportAll, setLoadingExportAll] = useState(false)
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
id: 0,
|
||||
name: '',
|
||||
role: '',
|
||||
note: '',
|
||||
},
|
||||
validate: {
|
||||
name: (value) =>
|
||||
value.length === 0 ? 'Please enter project name' : null,
|
||||
role: (value) => (value.length === 0 ? 'Please enter role' : null),
|
||||
note: (value) => (value.length === 0 ? 'Please enter note' : null),
|
||||
},
|
||||
})
|
||||
|
||||
const getListUser = async () => {
|
||||
try {
|
||||
|
|
@ -92,14 +152,19 @@ const StaffEvaluation = () => {
|
|||
? moment(filterSearch.toDate).format('YYYY-MM-DD')
|
||||
: null,
|
||||
}
|
||||
|
||||
const user = listUsers.find(
|
||||
(el) => el.id.toString() === filterSearch.userID,
|
||||
)
|
||||
setLoadingExport(true)
|
||||
const res = await getDownloadFile(evaluation, params)
|
||||
|
||||
if (res.status) {
|
||||
const fileURL = window.URL.createObjectURL(new Blob([res.data]))
|
||||
const fileLink = document.createElement('a')
|
||||
|
||||
const fileName = `EXPORT_SPRINT_REVIEW_AND_TECHNICAL_EVALUATION_${getFormattedDateTime()}.docx`
|
||||
const fileName = `STAFF_EVALUATION_${user?.name
|
||||
?.split(' ')
|
||||
.join('_')}_${getFormattedDateTime()}.docx`
|
||||
|
||||
fileLink.href = fileURL
|
||||
fileLink.setAttribute('download', fileName)
|
||||
|
|
@ -108,6 +173,43 @@ const StaffEvaluation = () => {
|
|||
fileLink.click()
|
||||
fileLink.remove()
|
||||
}
|
||||
setLoadingExport(false)
|
||||
} catch (error: any) {
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: error.message ?? error,
|
||||
color: 'red',
|
||||
})
|
||||
}
|
||||
return []
|
||||
}
|
||||
const downloadFileAll = async (filterSearch: Filter) => {
|
||||
try {
|
||||
const params = {
|
||||
fromDate: filterSearch.fromDate
|
||||
? moment(filterSearch.fromDate).format('YYYY-MM-DD')
|
||||
: null,
|
||||
toDate: filterSearch.toDate
|
||||
? moment(filterSearch.toDate).format('YYYY-MM-DD')
|
||||
: null,
|
||||
}
|
||||
setLoadingExportAll(true)
|
||||
const res = await getDownloadFile(evaluationReportAllUsers, params)
|
||||
|
||||
if (res.status) {
|
||||
const fileURL = window.URL.createObjectURL(new Blob([res.data]))
|
||||
const fileLink = document.createElement('a')
|
||||
|
||||
const fileName = `STAFF_EVALUATION_All_USERS_${getFormattedDateTime()}.docx`
|
||||
|
||||
fileLink.href = fileURL
|
||||
fileLink.setAttribute('download', fileName)
|
||||
document.body.appendChild(fileLink)
|
||||
|
||||
fileLink.click()
|
||||
fileLink.remove()
|
||||
}
|
||||
setLoadingExportAll(false)
|
||||
} catch (error: any) {
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
|
|
@ -168,12 +270,39 @@ const StaffEvaluation = () => {
|
|||
return []
|
||||
}
|
||||
|
||||
const getListProjectReview = async (filterSearch: Filter) => {
|
||||
try {
|
||||
const params = {
|
||||
userID: filterSearch.userID ?? '',
|
||||
fromDate: filterSearch.fromDate
|
||||
? moment(filterSearch.fromDate).format('YYYY-MM-DD')
|
||||
: null,
|
||||
toDate: filterSearch.toDate
|
||||
? moment(filterSearch.toDate).format('YYYY-MM-DD')
|
||||
: null,
|
||||
}
|
||||
const res = await get(projectReview, params)
|
||||
if (res.status) {
|
||||
return res.data
|
||||
}
|
||||
} catch (error: any) {
|
||||
notifications.show({
|
||||
title: 'Error',
|
||||
message: error.message ?? error,
|
||||
color: 'red',
|
||||
})
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (filter?.userID) {
|
||||
setLoading(true)
|
||||
const fetchData = async () => {
|
||||
const result = await getListProfilesData(filter)
|
||||
const resultProject = await getListProjectReview(filter)
|
||||
setDataProfile(result ?? [])
|
||||
setDataProjectReview(resultProject ?? [])
|
||||
setLoading(false)
|
||||
}
|
||||
fetchData()
|
||||
|
|
@ -284,6 +413,148 @@ const StaffEvaluation = () => {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const columnsProjectReview = [
|
||||
// {
|
||||
// name: 'id',
|
||||
// size: '5%',
|
||||
// header: 'Num',
|
||||
// render: (row: any) => {
|
||||
// return (
|
||||
// <Box fw={500} ta="center" p={4}>
|
||||
// {row?.id ? row.id : ''}
|
||||
// </Box>
|
||||
// )
|
||||
// },
|
||||
// },
|
||||
{
|
||||
name: 'name',
|
||||
size: '15%',
|
||||
header: 'Project Name',
|
||||
},
|
||||
{
|
||||
name: 'role',
|
||||
size: '15%',
|
||||
header: 'Role',
|
||||
},
|
||||
{
|
||||
name: 'note',
|
||||
size: '',
|
||||
header: 'Note',
|
||||
},
|
||||
{
|
||||
name: 'created_at',
|
||||
size: '10%',
|
||||
header: 'Created at',
|
||||
render: (row: any) => {
|
||||
if (row?.created_at)
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{moment(row?.created_at).format('DD/MM/YYYY HH:mm:ss')}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'updated_at',
|
||||
size: '10%',
|
||||
header: 'Last update',
|
||||
render: (row: any) => {
|
||||
if (row?.updated_at)
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{moment(row?.updated_at).format('DD/MM/YYYY HH:mm:ss')}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '#',
|
||||
size: '5%',
|
||||
header: 'Action',
|
||||
render: (row: DataProjectReview) => {
|
||||
return (
|
||||
<Box className={classes.optionIcon}>
|
||||
<IconEdit
|
||||
className={classes.editIcon}
|
||||
onClick={() => {
|
||||
setAction('edit')
|
||||
form.setValues(row)
|
||||
}}
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
<IconX
|
||||
className={classes.deleteIcon}
|
||||
onClick={() => {
|
||||
setAction('delete')
|
||||
setItem(row)
|
||||
}}
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const handleCreate = async (values: DataProjectReview) => {
|
||||
try {
|
||||
const { id, ...data } = values
|
||||
const res = await post(projectReviewAdd, {
|
||||
...data,
|
||||
user_id: filter.userID,
|
||||
})
|
||||
if (res.id) {
|
||||
setAction('')
|
||||
form.reset()
|
||||
const resultProject = await getListProjectReview(filter)
|
||||
setDataProjectReview(resultProject ?? [])
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpdate = async (values: DataProjectReview) => {
|
||||
try {
|
||||
const res = await update(projectReviewUpdate, {
|
||||
...values,
|
||||
user_id: filter.userID,
|
||||
})
|
||||
if (res) {
|
||||
setAction('')
|
||||
form.reset()
|
||||
const resultProject = await getListProjectReview(filter)
|
||||
setDataProjectReview(resultProject ?? [])
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
await Xdelete(projectReviewDelete, { id: id }, async () => {
|
||||
const resultProject = await getListProjectReview(filter)
|
||||
setDataProjectReview(resultProject ?? [])
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={classes.title}>
|
||||
|
|
@ -291,10 +562,38 @@ const StaffEvaluation = () => {
|
|||
<Text>Admin/</Text>
|
||||
Staff Evaluation
|
||||
</h3>
|
||||
<Box
|
||||
w="20%"
|
||||
display={'flex'}
|
||||
style={{ justifyContent: 'flex-end' }}
|
||||
mr={10}
|
||||
>
|
||||
{loadingExportAll ? (
|
||||
<Button
|
||||
disabled
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
width: '135px',
|
||||
}}
|
||||
onClick={() => {}}
|
||||
>
|
||||
<Loader size={'sm'} color="green" type="oval" m={'0 auto'} />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
// m={5}
|
||||
style={{ display: 'flex', width: '135px' }}
|
||||
onClick={() => downloadFileAll(filter)}
|
||||
>
|
||||
Export all user
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
<Box w="100%" display={'flex'} mt={15} ml={10}>
|
||||
<Box w="50%" display={'flex'}>
|
||||
<Box w="80%" display={'flex'} style={{ alignItems: 'center' }}>
|
||||
<Text
|
||||
mr={'xs'}
|
||||
style={{ alignContent: 'center' }}
|
||||
|
|
@ -304,7 +603,7 @@ const StaffEvaluation = () => {
|
|||
User:
|
||||
</Text>
|
||||
<Select
|
||||
style={{ width: '30%' }}
|
||||
style={{ width: '20%' }}
|
||||
label={''}
|
||||
placeholder="Select user"
|
||||
maxLength={255}
|
||||
|
|
@ -317,131 +616,340 @@ const StaffEvaluation = () => {
|
|||
value={filter.userID}
|
||||
onChange={(e) => setFilter({ ...filter, userID: e! })}
|
||||
/>
|
||||
<Box
|
||||
display={'flex'}
|
||||
mr={10}
|
||||
ms={10}
|
||||
style={{ alignItems: 'center' }}
|
||||
>
|
||||
<Text
|
||||
mr={'xs'}
|
||||
style={{ alignContent: 'center' }}
|
||||
fw={600}
|
||||
size={'md'}
|
||||
>
|
||||
From Date:
|
||||
</Text>
|
||||
|
||||
<DateInput
|
||||
placeholder="Select date"
|
||||
clearable
|
||||
size="xs"
|
||||
required
|
||||
label={''}
|
||||
value={filter.fromDate ? new Date(filter.fromDate) : null}
|
||||
valueFormat="DD/MM/YYYY"
|
||||
onChange={(e) => setFilter({ ...filter, fromDate: e! })}
|
||||
></DateInput>
|
||||
</Box>
|
||||
|
||||
<Box display={'flex'} mr={10} style={{ alignItems: 'center' }}>
|
||||
<Text
|
||||
mr={'xs'}
|
||||
style={{ alignContent: 'center' }}
|
||||
fw={600}
|
||||
size={'md'}
|
||||
>
|
||||
To Date:
|
||||
</Text>
|
||||
<DateInput
|
||||
placeholder="Select date"
|
||||
clearable
|
||||
size="xs"
|
||||
required
|
||||
label={''}
|
||||
value={filter.toDate ? new Date(filter.toDate) : null}
|
||||
valueFormat="DD/MM/YYYY"
|
||||
onChange={(e) => setFilter({ ...filter, toDate: e! })}
|
||||
></DateInput>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
w="50%"
|
||||
w="20%"
|
||||
display={'flex'}
|
||||
style={{ justifyContent: 'flex-end' }}
|
||||
mr={10}
|
||||
>
|
||||
<Button
|
||||
// m={5}
|
||||
style={{ display: filter.userID != '' ? 'flex' : 'none' }}
|
||||
onClick={() => downloadFile(filter)}
|
||||
>
|
||||
Export
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box className={classes.userInfoSection} display="flex">
|
||||
<Box className={classes.projectInvolvement}>
|
||||
<Box
|
||||
w="100%"
|
||||
display={'flex'}
|
||||
mt={15}
|
||||
>
|
||||
<Box display={'flex'} mr={10}>
|
||||
<Text
|
||||
mr={'xs'}
|
||||
style={{ alignContent: 'center' }}
|
||||
fw={600}
|
||||
size={'md'}
|
||||
>
|
||||
From Date:
|
||||
</Text>
|
||||
|
||||
<DateInput
|
||||
placeholder="Select date"
|
||||
clearable
|
||||
size='xs'
|
||||
required
|
||||
label={''}
|
||||
value={filter.fromDate ? new Date(filter.fromDate) : null}
|
||||
valueFormat="DD/MM/YYYY"
|
||||
onChange={(e) => setFilter({ ...filter, fromDate: e! })}
|
||||
></DateInput>
|
||||
</Box>
|
||||
|
||||
<Box display={'flex'} mr={10}>
|
||||
<Text
|
||||
mr={'xs'}
|
||||
style={{ alignContent: 'center' }}
|
||||
fw={600}
|
||||
size={'md'}
|
||||
>
|
||||
To Date:
|
||||
</Text>
|
||||
<DateInput
|
||||
placeholder="Select date"
|
||||
clearable
|
||||
size='xs'
|
||||
required
|
||||
label={''}
|
||||
value={filter.toDate ? new Date(filter.toDate) : null}
|
||||
valueFormat="DD/MM/YYYY"
|
||||
onChange={(e) => setFilter({ ...filter, toDate: e! })}
|
||||
></DateInput>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
style={{
|
||||
marginTop: '10%',
|
||||
textAlign: 'center',
|
||||
display: loading ? 'block' : 'none',
|
||||
// display: 'none',
|
||||
}}
|
||||
>
|
||||
<Loader size={'sm'} color="green" type="bars" m={'0 auto'} />
|
||||
<Text fw={600} c={'gray'}>
|
||||
Loading . . .
|
||||
</Text>
|
||||
</Box>
|
||||
{!loading && dataProfile.length == 0 && (
|
||||
<Box
|
||||
{loadingExport ? (
|
||||
<Button
|
||||
disabled
|
||||
style={{
|
||||
marginTop: '10%',
|
||||
textAlign: 'center',
|
||||
display: 'block',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
width: '80px',
|
||||
}}
|
||||
onClick={() => {}}
|
||||
>
|
||||
<Text fw={600} c={'gray'}>
|
||||
No Data Sprint
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
{!loading && (
|
||||
<ProjectInvolvement dataProfile={dataProfile} page="admin" />
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box className={classes.sidebar}>
|
||||
<Title order={3} className={classes.titleSidebar}>
|
||||
Technicals
|
||||
</Title>
|
||||
{loadingTechnical ? (
|
||||
<Box
|
||||
style={{
|
||||
marginTop: '10%',
|
||||
textAlign: 'center',
|
||||
display: 'block',
|
||||
}}
|
||||
>
|
||||
<Loader size={'sm'} color="green" type="bars" m={'0 auto'} />
|
||||
<Text fw={600} c={'gray'}>
|
||||
Loading . . .
|
||||
</Text>
|
||||
</Box>
|
||||
<Loader size={'sm'} color="green" type="oval" m={'0 auto'} />
|
||||
</Button>
|
||||
) : (
|
||||
<DataTableAll
|
||||
data={dataTechnical}
|
||||
columns={columns}
|
||||
size=""
|
||||
searchInput
|
||||
infoTotal={infoTotal()}
|
||||
/>
|
||||
<Button
|
||||
// m={5}
|
||||
style={{
|
||||
display: filter.userID != '' ? 'flex' : 'none',
|
||||
width: '80px',
|
||||
}}
|
||||
onClick={() => downloadFile(filter)}
|
||||
>
|
||||
Export
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<Tabs mt={8} variant="outline" defaultValue="general">
|
||||
<Tabs.List>
|
||||
<Tabs.Tab
|
||||
value="general"
|
||||
leftSection={<IconClearAll size={16} color="teal" />}
|
||||
>
|
||||
<span style={{ fontSize: '16px' }}>General</span>
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
value="project_review"
|
||||
leftSection={<IconPresentationAnalytics size={16} color="blue" />}
|
||||
>
|
||||
<span style={{ fontSize: '16px' }}>Project review</span>
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Panel value="general">
|
||||
<Box className={classes.userInfoSection} display="flex">
|
||||
<Box className={classes.projectInvolvement}>
|
||||
<Box
|
||||
style={{
|
||||
marginTop: '10%',
|
||||
textAlign: 'center',
|
||||
display: loading ? 'block' : 'none',
|
||||
// display: 'none',
|
||||
}}
|
||||
>
|
||||
<Loader size={'sm'} color="green" type="bars" m={'0 auto'} />
|
||||
<Text fw={600} c={'gray'}>
|
||||
Loading . . .
|
||||
</Text>
|
||||
</Box>
|
||||
{!loading && dataProfile.length == 0 && (
|
||||
<Box
|
||||
style={{
|
||||
marginTop: '10%',
|
||||
textAlign: 'center',
|
||||
display: 'block',
|
||||
}}
|
||||
>
|
||||
<Text fw={600} c={'gray'}>
|
||||
No Data Sprint
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
{!loading && (
|
||||
<ProjectInvolvement dataProfile={dataProfile} page="admin" />
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box className={classes.sidebar}>
|
||||
<Title order={3} className={classes.titleSidebar}>
|
||||
Technicals
|
||||
</Title>
|
||||
{loadingTechnical ? (
|
||||
<Box
|
||||
style={{
|
||||
marginTop: '10%',
|
||||
textAlign: 'center',
|
||||
display: 'block',
|
||||
}}
|
||||
>
|
||||
<Loader size={'sm'} color="green" type="bars" m={'0 auto'} />
|
||||
<Text fw={600} c={'gray'}>
|
||||
Loading . . .
|
||||
</Text>
|
||||
</Box>
|
||||
) : (
|
||||
<DataTableAll
|
||||
data={dataTechnical}
|
||||
columns={columns}
|
||||
size=""
|
||||
searchInput
|
||||
infoTotal={infoTotal()}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel value="project_review">
|
||||
<Box className={classes.userInfoSection} display="flex">
|
||||
{loading ? (
|
||||
<Box
|
||||
style={{ width: '100%', display: loading ? '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>
|
||||
) : (
|
||||
<DataTableAll
|
||||
data={dataProjectReview}
|
||||
columns={columnsProjectReview}
|
||||
size=""
|
||||
searchInput
|
||||
// infoTotal={infoTotal()}
|
||||
componentRight={
|
||||
<Box
|
||||
w="100%"
|
||||
display={'flex'}
|
||||
style={{ justifyContent: 'flex-end' }}
|
||||
mr={10}
|
||||
>
|
||||
<Button
|
||||
color="teal"
|
||||
style={{ display: filter.userID != '' ? 'flex' : 'none' }}
|
||||
onClick={() => {
|
||||
setAction('add')
|
||||
form.reset()
|
||||
}}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
|
||||
{/* Add/Edit User modal */}
|
||||
<Modal
|
||||
opened={action === 'add' || action === 'edit'}
|
||||
onClose={() => {
|
||||
setAction('')
|
||||
form.reset()
|
||||
}}
|
||||
title={
|
||||
<Text pl={'sm'} fw={700} fz={'lg'}>
|
||||
{action === 'add' ? 'Add Review' : 'Update Review'}
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
<form
|
||||
onSubmit={form.onSubmit(async (values) => {
|
||||
setDisableBtn(true)
|
||||
action === 'edit'
|
||||
? await handleUpdate(values)
|
||||
: await handleCreate(values)
|
||||
setDisableBtn(false)
|
||||
})}
|
||||
>
|
||||
<Box pl={'md'} pr={'md'}>
|
||||
<TextInput
|
||||
placeholder="Input name"
|
||||
label={
|
||||
<span>
|
||||
Name project: <span style={{ color: 'red' }}>*</span>
|
||||
</span>
|
||||
}
|
||||
mb={'md'}
|
||||
value={form.values.name}
|
||||
error={form.errors.name}
|
||||
onChange={(e) => form.setFieldValue('name', e.target.value)}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
placeholder="Input role"
|
||||
label={
|
||||
<span>
|
||||
Role: <span style={{ color: 'red' }}>*</span>
|
||||
</span>
|
||||
}
|
||||
mb={'md'}
|
||||
value={form.values.role}
|
||||
error={form.errors.role}
|
||||
onChange={(e) => form.setFieldValue('role', e.target.value)}
|
||||
/>
|
||||
<Textarea
|
||||
placeholder="Input notes"
|
||||
rows={4}
|
||||
label={
|
||||
<span>
|
||||
Note: <span style={{ color: 'red' }}>*</span>
|
||||
</span>
|
||||
}
|
||||
mb={'md'}
|
||||
value={form.values.note}
|
||||
error={form.errors.note}
|
||||
onChange={(e) => form.setFieldValue('note', e.target.value)}
|
||||
/>
|
||||
<Box ta={'center'}>
|
||||
{action === 'add' ? (
|
||||
<Button
|
||||
mt={'lg'}
|
||||
bg={'green'}
|
||||
type="submit"
|
||||
disabled={disableBtn}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
mt={'lg'}
|
||||
bg={'green'}
|
||||
type="submit"
|
||||
disabled={disableBtn}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
<Dialog
|
||||
className={classes.dialog}
|
||||
opened={action === 'delete'}
|
||||
withCloseButton
|
||||
onClose={() => setAction('')}
|
||||
size="lg"
|
||||
radius="md"
|
||||
position={{ top: 30, right: 10 }}
|
||||
>
|
||||
<Text className={classes.dialogText} size="sm" mb="xs" fw={500}>
|
||||
Do you want to delete this review?
|
||||
<Group justify="center" m={10}>
|
||||
<Button
|
||||
disabled={activeBtn}
|
||||
fw={700}
|
||||
size="xs"
|
||||
variant="light"
|
||||
onClick={async () => {
|
||||
setActiveBtn(true)
|
||||
await handleDelete(item.id)
|
||||
setActiveBtn(false)
|
||||
setAction('')
|
||||
}}
|
||||
>
|
||||
Yes
|
||||
</Button>
|
||||
<Button
|
||||
fw={700}
|
||||
size="xs"
|
||||
variant="light"
|
||||
onClick={() => setAction('')}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Group>
|
||||
</Text>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -655,9 +655,7 @@ const Timekeeping = () => {
|
|||
data={Array.from({ length: 12 }, (_, index) => {
|
||||
return {
|
||||
value: (1 + index).toString(),
|
||||
label: (1 + index).toString(),
|
||||
disabled:
|
||||
1 + index > parseInt(moment(Date.now()).format('MM')),
|
||||
label: (1 + index).toString()
|
||||
}
|
||||
})}
|
||||
onChange={(e) => {
|
||||
|
|
@ -715,10 +713,11 @@ const Timekeeping = () => {
|
|||
<Button
|
||||
onClick={() => setExportModalOpened(true)}
|
||||
size="xs"
|
||||
ml="xl"
|
||||
ml="md"
|
||||
bg={'green'}
|
||||
leftSection={<IconFileExcel size={16} />}
|
||||
>
|
||||
Export Excel
|
||||
Export
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
pyttsx3
|
||||
openpyxl
|
||||
pyzbar
|
||||
opencv-python
|
||||
qrcode
|
||||
pyautogui
|
||||
requests
|
||||
pillow
|
||||
#MacOS
|
||||
python3 -m venv path/to/venv
|
||||
source path/to/venv/bin/activate
|
||||
pip install pyttsx3 openpyxl pyzbar opencv-python qrcode pyautogui requests pillow
|
||||
pip install wheel setuptools pip --upgrade
|
||||
pip install git+https://github.com/ageitgey/face_recognition_models --verbose
|
||||
pip install face_recognition
|
||||
|
|
@ -6,6 +6,10 @@ import pyautogui
|
|||
from pyzbar import pyzbar
|
||||
from datetime import datetime
|
||||
import requests
|
||||
import face_recognition
|
||||
import numpy as np
|
||||
import pickle
|
||||
import time
|
||||
# Khởi tạo danh sách rỗng để lưu trữ thông tin người dùng
|
||||
user_data = []
|
||||
history = []
|
||||
|
|
@ -14,6 +18,7 @@ screen_height = 1100
|
|||
WINDOW_QR_CODE = "QR Code"
|
||||
WINDOW_TRACKING = "Tracking"
|
||||
WINDOW_HISTORY = "History"
|
||||
# URL_API = "http://localhost:8000/api/v1"
|
||||
URL_API = "https://ms.prology.net/api/v1"
|
||||
data = [0]
|
||||
# Hàm thông báo bằng giọng nói
|
||||
|
|
@ -177,10 +182,10 @@ def process_qr_code(frame):
|
|||
cv.resizeWindow(WINDOW_QR_CODE, screen_width, screen_height)
|
||||
cv.imshow(WINDOW_QR_CODE, frame)
|
||||
cv.moveWindow(WINDOW_QR_CODE, 10, 10)
|
||||
cv.waitKey(5000) # Chờ 5 giây
|
||||
cv.waitKey(3000) # Chờ 5 giây
|
||||
screenshot_window(file_name)
|
||||
send_image(id_log, file_name)
|
||||
cv.destroyWindow(WINDOW_QR_CODE)
|
||||
send_image(id_log, file_name)
|
||||
else:
|
||||
display_text(frame, f"QR invalid", (25, 25), 0.7, (6, 6, 255), 2)
|
||||
display_text(frame, f"Failed", (25, 50), 0.7, (6, 6, 255), 2)
|
||||
|
|
@ -193,8 +198,76 @@ def process_qr_code(frame):
|
|||
cv.destroyWindow(WINDOW_QR_CODE)
|
||||
return frame
|
||||
|
||||
# Hàm để xử lý quá trình quét mã QR code
|
||||
def process_face_detect(text, frame):
|
||||
if text.endswith("\n\n"):
|
||||
file_name = ""
|
||||
status = ""
|
||||
id_log = 0
|
||||
if text not in [user["name"] for user in user_data]:
|
||||
print(f"{text} đã check in lúc {datetime.now()}")
|
||||
status += "check in"
|
||||
file_name+=text.split('\n')[0]+"_"+f"{status}_at_{datetime.now().strftime("%Y_%m_%d_%H_%M_%S")}.png"
|
||||
# screenshot_window(file_name)
|
||||
res = check_in(text, frame, text)
|
||||
id_log = res.get('data').get('id')
|
||||
else:
|
||||
print(f"{text} đã check out lúc {datetime.now()}")
|
||||
status += "check out"
|
||||
file_name+=text.split('\n')[0]+"_"+f"{status}_at_{datetime.now().strftime("%Y_%m_%d_%H_%M_%S")}.png"
|
||||
# screenshot_window(file_name)
|
||||
res = check_out(text, frame, text)
|
||||
id_log = res.get('data').get('id')
|
||||
screenshot_window(file_name)
|
||||
cv.namedWindow(WINDOW_QR_CODE, cv.WINDOW_NORMAL)
|
||||
cv.resizeWindow(WINDOW_QR_CODE, screen_width, screen_height)
|
||||
cv.imshow(WINDOW_QR_CODE, frame)
|
||||
cv.moveWindow(WINDOW_QR_CODE, 10, 10)
|
||||
# Chạy vòng lặp không chặn trong 2 giây
|
||||
cv.waitKey(2000) # Chờ 10ms
|
||||
cv.destroyWindow(WINDOW_QR_CODE)
|
||||
send_image(id_log, file_name)
|
||||
else:
|
||||
display_text(frame, f"QR invalid", (25, 25), 0.7, (6, 6, 255), 2)
|
||||
display_text(frame, f"Failed", (25, 50), 0.7, (6, 6, 255), 2)
|
||||
speak("Failed")
|
||||
cv.namedWindow(WINDOW_QR_CODE, cv.WINDOW_NORMAL)
|
||||
cv.resizeWindow(WINDOW_QR_CODE, screen_width, screen_height)
|
||||
cv.imshow(WINDOW_QR_CODE, frame)
|
||||
cv.moveWindow(WINDOW_QR_CODE, 10, 10)
|
||||
cv.waitKey(2000)
|
||||
cv.destroyWindow(WINDOW_QR_CODE)
|
||||
return frame
|
||||
|
||||
datasetPath = "../DetectFace/dataset"
|
||||
listFilesPath = '../DetectFace/listFiles.pkl'
|
||||
images = []
|
||||
classNames = []
|
||||
lisFileTrain = []
|
||||
|
||||
if os.path.exists(listFilesPath):
|
||||
with open(listFilesPath, 'rb') as f:
|
||||
lisFileTrain = pickle.load(f)
|
||||
else:
|
||||
lisFileTrain = os.listdir(datasetPath)
|
||||
with open(listFilesPath, 'wb') as f:
|
||||
pickle.dump(lisFileTrain, f)
|
||||
for file in lisFileTrain:
|
||||
classNames.append(os.path.splitext(file)[0].split('_')[0])
|
||||
|
||||
def encodeImgs(save_path="../DetectFace/encodings.pkl"):
|
||||
if os.path.exists(save_path):
|
||||
print(f"Loading encodings from {save_path}...")
|
||||
with open(save_path, "rb") as f:
|
||||
return pickle.load(f)
|
||||
|
||||
encodeListKnow = encodeImgs()
|
||||
print("Load data success")
|
||||
# Khởi tạo camera
|
||||
|
||||
def main():
|
||||
recognized_faces = {}
|
||||
name_history = {}
|
||||
cap = cv.VideoCapture(0)
|
||||
face_cascade = cv.CascadeClassifier(cv.data.haarcascades + 'haarcascade_frontalface_default.xml')
|
||||
cv.namedWindow(WINDOW_TRACKING, cv.WINDOW_NORMAL)
|
||||
|
|
@ -203,20 +276,67 @@ def main():
|
|||
ret, frame = cap.read()
|
||||
if not ret:
|
||||
break
|
||||
|
||||
frameS = cv.resize(frame, (0,0), None, fx=0.5, fy=0.5)
|
||||
frameS = cv.cvtColor(frameS, cv.COLOR_BGR2RGB)
|
||||
|
||||
faceCurFrame = face_recognition.face_locations(frameS, model='hog')
|
||||
encodeCurFrame = face_recognition.face_encodings(frameS)
|
||||
frame = process_qr_code(frame)
|
||||
current_time = time.time()
|
||||
for encodeFace, faceLoc in zip(encodeCurFrame, faceCurFrame):
|
||||
matches = face_recognition.compare_faces(encodeListKnow, encodeFace)
|
||||
faceDis = face_recognition.face_distance(encodeListKnow, encodeFace)
|
||||
# print(faceDis)
|
||||
matchIndex = np.argmin(faceDis)
|
||||
|
||||
if faceDis[matchIndex] < 0.35:
|
||||
name = classNames[matchIndex].upper()
|
||||
# If the face is recognized, track the timestamp
|
||||
if name not in recognized_faces:
|
||||
recognized_faces[name] = current_time # Store first detection time
|
||||
else:
|
||||
elapsed_time = current_time - recognized_faces[name]
|
||||
if (name not in name_history) or (current_time - name_history[name] >= 60):
|
||||
if elapsed_time >= 2.5: # If face is seen for 2s, execute script
|
||||
process_face_detect(f"{name}\n{"Staff"}\n\n", frame)
|
||||
name_history[name] = time.time()
|
||||
del recognized_faces[name]
|
||||
else:
|
||||
display_text(frame, f"Checking: "+str(round((elapsed_time/2.5)*100,2))+"%", (700, 55), 1, (0, 255, 255), 2)
|
||||
else:
|
||||
display_text(frame, f"Checked. Try after {round(60-(current_time - name_history[name]),0)}s", (600, 55), 1, (0, 255, 255), 2)
|
||||
else:
|
||||
name = "Unknow"
|
||||
recognized_faces = {}
|
||||
display_text(frame, f"Face not found - use QRcode", (20, 55), 0.7, (6, 6, 255), 2)
|
||||
y1, x2, y2, x1 = faceLoc
|
||||
y1, x2, y2, x1 = y1*2, x2*2, y2*2, x1*2
|
||||
cv.rectangle(frame, (x1,y1), (x2,y2), (0,255,0), 2)
|
||||
cv.putText(frame, name + f"({(1 - round(faceDis[matchIndex], 2))*100}%)", (x1, y1), cv.FONT_HERSHEY_COMPLEX, 0.8, (0,255,0), 2)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Convert the frame to grayscale
|
||||
gray_frame = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
|
||||
# gray_frame = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
|
||||
|
||||
# Detect faces in the frame
|
||||
faces = face_cascade.detectMultiScale(gray_frame, scaleFactor=1.1, minNeighbors=25, minSize=(30, 30))
|
||||
# # Detect faces in the frame
|
||||
# faces = face_cascade.detectMultiScale(gray_frame, scaleFactor=1.1, minNeighbors=25, minSize=(30, 30))
|
||||
|
||||
# Draw rectangles around the faces
|
||||
if len(faces) == 1:
|
||||
for (x, y, w, h) in faces:
|
||||
cv.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
|
||||
display_text(frame, f"Face detected", (430, 25), 0.7, (0, 255, 0), 2)
|
||||
frame = process_qr_code(frame)
|
||||
else:
|
||||
display_text(frame, f"Face not found", (430, 25), 0.7, (6, 6, 255), 2)
|
||||
# # Draw rectangles around the faces
|
||||
# if len(faces) == 1:
|
||||
# for (x, y, w, h) in faces:
|
||||
# cv.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
|
||||
# display_text(frame, f"Face detected", (430, 25), 0.7, (0, 255, 0), 2)
|
||||
# frame = process_qr_code(frame)
|
||||
# else:
|
||||
# display_text(frame, f"Face not found", (430, 25), 0.7, (6, 6, 255), 2)
|
||||
cv.imshow(WINDOW_TRACKING, frame)
|
||||
cv.moveWindow(WINDOW_TRACKING, 10, 10)
|
||||
if cv.waitKey(1) == ord('q'):
|
||||
|
|
@ -225,4 +345,5 @@ def main():
|
|||
cap.release()
|
||||
cv.destroyAllWindows()
|
||||
|
||||
main()
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue