Merge pull request 'master' (#103) from master into dev

Reviewed-on: #103
This commit is contained in:
joseph 2025-02-07 19:00:27 +11:00
commit 4d94e26bb9
21 changed files with 1676 additions and 152 deletions

View File

@ -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);
}
}

View File

@ -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]);
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Modules\Admin\app\Http\Controllers;
use App\Traits\AnalyzeData;
class DataAnalyzer
{
use AnalyzeData;
}

View File

@ -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);
}
}

View File

@ -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([

View File

@ -101,7 +101,7 @@ return [
|
*/
'ttl' => env('JWT_TTL', 60*24),
'ttl' => env('JWT_TTL', 60*24*365),
/*
|--------------------------------------------------------------------------

View File

@ -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');
}
};

2
DetectFace/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
dataset
test

28
DetectFace/collectData.py Normal file
View File

@ -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)

77
DetectFace/detect.py Normal file
View File

@ -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()

BIN
DetectFace/encodings.pkl Normal file

Binary file not shown.

BIN
DetectFace/listFiles.pkl Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

425
FRONTEND/install_nvm.sh Normal file
View File

@ -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 #

View File

@ -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'

View File

@ -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

View File

@ -55,6 +55,8 @@
display: flex;
margin-top: 20px;
gap: 10px;
max-height: 72vh;
overflow-y: scroll;
}
.titleSidebar {

View File

@ -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>
)
}

View File

@ -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>

View File

@ -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

View File

@ -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()