diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/CriteriasController.php b/BACKEND/Modules/Admin/app/Http/Controllers/CriteriasController.php
new file mode 100644
index 0000000..b51ccca
--- /dev/null
+++ b/BACKEND/Modules/Admin/app/Http/Controllers/CriteriasController.php
@@ -0,0 +1,191 @@
+find($sprintId);
+ // Nếu không tìm thấy sprint, trả về response lỗi
+ if (!$sprint) {
+ return AbstractController::ResultError('Sprint not found', [], 404);
+ }
+
+ // Trả về thông tin sprint và criterias
+ return AbstractController::ResultSuccess($sprint);
+ }
+
+ public function convertDataReponse($criterias)
+ {
+ $data = [];
+ foreach ($criterias as $key => $criteria) {
+ $data[$key]["criteria_name"] = $criteria->name;
+ $data[$key]["description"] = $criteria->description;
+ $data[$key]["type"] = $criteria->type;
+ foreach ($criteria->users as $keyChild => $user) {
+ $dataChild[$keyChild]["user_id"] = $user->id;
+ $dataChild[$keyChild]["user_name"] = $user->name;
+ $dataChild[$keyChild]["email"] = $user->email;
+ $dataChild[$keyChild]["point"] = $user->pivot->point;
+ $dataChild[$keyChild]["note"] = $user->pivot->note;
+ $dataChild[$keyChild]["sprint_id"] = $user->pivot->sprint_id;
+ }
+ $data[$key]['users'] = $dataChild;
+ }
+
+ return $data;
+ }
+ /**
+ * Get all criterias of a user
+ *
+ * @param int $userId The id of the user
+ * @return \Illuminate\Database\Eloquent\Collection|null A collection of Criteria models or null if the user is not found
+ */
+ public function getCriteriasForUser($userId)
+ {
+ $criterias = Criteria::whereHas('users', function ($query) use ($userId) {
+ $query->where('user_id', $userId);
+ })->with(['users' => function ($query) use ($userId) {
+ $query->where('user_id', $userId);
+ }])->get();
+
+ $data = self::convertDataReponse($criterias);
+ return AbstractController::ResultSuccess($data);
+ }
+
+ /**
+ * Get all criterias of a user for a sprint
+ *
+ * @param int $userId The id of the user
+ * @param int $sprintId The id of the sprint
+ * @return \Illuminate\Database\Eloquent\Collection|null A collection of Criteria models or null if the user or sprint is not found
+ */
+ public function getCriteriasForUserBySprint($userId, $sprintId)
+ {
+ $criterias = Criteria::whereHas('users', function ($query) use ($userId, $sprintId) {
+ $query->where('user_id', $userId)->where('sprint_id', $sprintId);
+ })->with(['users' => function ($query) use ($userId, $sprintId) {
+ $query->where('user_id', $userId)->where('sprint_id', $sprintId);
+ }])->get();
+
+ $data = self::convertDataReponse($criterias);
+ return AbstractController::ResultSuccess($data);
+ }
+
+ /**
+ * Get all criterias
+ *
+ * @return \Illuminate\Database\Eloquent\Collection A collection of Criteria models
+ */
+ public function getAllCriterias(Request $request)
+ {
+ if ($request->type) {
+ $responseData = Criteria::where('type', $request->type)->get();
+ } else {
+ $responseData = Criteria::all();
+ }
+ return AbstractController::ResultSuccess($responseData);
+ }
+
+ /**
+ * Update or create multiple SprintCriteria and UserCriteria records
+ *
+ * @param int $sprintId The id of the sprint
+ * @param array $criteriaData An array of criteria data. Each element of the array should be an associative array with the following keys:
+ * - criteria_id: The id of the criteria
+ * - point: The point of the criteria
+ * - expect_result: The expected result of the criteria
+ * - actual_result: The actual result of the criteria
+ * - note: The note of the criteria
+ * - user_id (optional): The id of the user
+ * - user_point (optional): The point of the user (default is the same as point)
+ * - user_note (optional): The note of the user (default is the same as note)
+ * @return bool True if successful, false otherwise
+ */
+ public function updateCriteriasForSprint($sprintId, Request $request)
+ {
+ $user = auth('admins')->user();
+
+ // Tạo hoặc lấy Sprint
+ $sprint = Sprint::firstOrCreate(['id' => $sprintId], [
+ 'name' => 'Default Sprint Name',
+ 'start_date' => now(),
+ 'end_date' => now()->addDays(7),
+ 'project_id' => 1,
+ ]);
+
+ $criteriaData = $request->criterias;
+
+ foreach ($criteriaData as $data) {
+ // print_r($data);
+ $criteriaId = (int)$data['criteria_id'];
+
+ // Kiểm tra xem criteria có tồn tại không
+ $criteria = Criteria::find($criteriaId);
+ if (!$criteria) {
+ // Nếu không tồn tại, có thể ném ra ngoại lệ hoặc trả về lỗi
+ return response()->json(['error' => 'Criteria ID ' . $criteriaId . ' does not exist'], 400);
+ }
+
+ $point = $data['point'];
+ $expectResult = $data['expect_result'];
+ $actualResult = $data['actual_result'];
+ $note = $data['note'];
+
+ // Cập nhật hoặc tạo mới SprintCriteria
+ SprintCriteria::updateOrCreate(
+ ['sprint_id' => $sprint->id, 'criteria_id' => $criteriaId],
+ ['point' => $point, 'expect_result' => $expectResult, 'actual_result' => $actualResult, 'note' => $note]
+ );
+
+ if (isset($data['users'])) {
+ // Xóa hết các bản ghi UserCriteria theo sprint_id và criteria_id
+ UserCriteria::where('criteria_id', $criteriaId)
+ ->where('sprint_id', $sprint->id)
+ ->delete();
+ foreach ($data['users'] as $userData) {
+ $userId = $userData['user_id'];
+ $userPoint = $userData['point'] ?? $point;
+ $userNote = $userData['note'] ?? $note;
+
+ // Chèn lại các bản ghi mới vào bảng UserCriteria
+ UserCriteria::create([
+ 'user_id' => $userId,
+ 'criteria_id' => (int)$criteriaId,
+ 'sprint_id' => $sprint->id,
+ 'point' => $userPoint,
+ 'note' => $userNote,
+ 'created_by' => $user->name
+ ]);
+ }
+ }
+ }
+
+ return AbstractController::ResultSuccess("");
+ }
+}
diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/JiraController.php b/BACKEND/Modules/Admin/app/Http/Controllers/JiraController.php
index 00e67ed..077229f 100644
--- a/BACKEND/Modules/Admin/app/Http/Controllers/JiraController.php
+++ b/BACKEND/Modules/Admin/app/Http/Controllers/JiraController.php
@@ -193,14 +193,15 @@ class JiraController extends Controller
], 200);
}
- private function customSort($a, $b, $order) {
+ private function customSort($a, $b, $order)
+ {
$pos_a = array_search(strtolower($a), $order);
$pos_b = array_search(strtolower($b), $order);
-
+
if ($pos_a === false || $pos_b === false) {
return 0;
}
-
+
return $pos_a - $pos_b;
}
@@ -224,7 +225,7 @@ class JiraController extends Controller
$totalTimeSpent = 0;
foreach ($issue['fields']['worklog']['worklogs'] as $worklog) {
$started = Carbon::parse($worklog['started']);
- $totalTimeSpent = $totalTimeSpent + ($worklog['timeSpentSeconds']/60/60);
+ $totalTimeSpent = $totalTimeSpent + ($worklog['timeSpentSeconds'] / 60 / 60);
if ($started->isSameDay($today) && $worklog['updateAuthor']['displayName'] == $user) {
$filteredWorklogs[] = $worklog;
}
@@ -238,7 +239,7 @@ class JiraController extends Controller
'totalTimeSpent' => $totalTimeSpent,
];
$tasksByUser[$user]['assignment'] = $log['tasksAssign'];
- uksort($tasksByUser[$user]["allStatus"], function($a, $b) use ($predefinedOrder) {
+ uksort($tasksByUser[$user]["allStatus"], function ($a, $b) use ($predefinedOrder) {
return $this->customSort($a, $b, $predefinedOrder);
});
}
@@ -270,4 +271,46 @@ class JiraController extends Controller
'status' => true
], 200);
}
+
+ public function getDetailsProjectsById(Request $request)
+ {
+ $id = $request->input('id');
+ $projects = $this->jiraService->getDetailsProjectsById($id);
+ return response()->json([
+ 'data' => $projects,
+ 'status' => true
+ ], 200);
+ }
+
+ public function getAllBoardByIdProjects(Request $request)
+ {
+ $id = $request->input('id');
+ $projects = $this->jiraService->getAllBoardByIdProjects($id);
+
+ return response()->json([
+ 'data' => $projects,
+ 'status' => true
+ ], 200);
+ }
+
+ public function getAllSprintByIdBoard(Request $request)
+ {
+ $id = $request->input('id');
+ $projects = $this->jiraService->getAllSprintByIdBoard($id);
+
+ return response()->json([
+ 'data' => $projects,
+ 'status' => true
+ ], 200);
+ }
+ public function getAllIssueByIdSprint(Request $request)
+ {
+ $id = $request->input('id');
+ $projects = $this->jiraService->getAllIssueByIdSprint($id);
+
+ return response()->json([
+ 'data' => $projects,
+ 'status' => true
+ ], 200);
+ }
}
diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/ProfileController.php b/BACKEND/Modules/Admin/app/Http/Controllers/ProfileController.php
new file mode 100644
index 0000000..95f4f4c
--- /dev/null
+++ b/BACKEND/Modules/Admin/app/Http/Controllers/ProfileController.php
@@ -0,0 +1,21 @@
+get();
+
+ if ($testReports->isEmpty()) {
+ return AbstractController::ResultError('No test reports found for this sprint', [], 404);
+ }
+
+ return AbstractController::ResultSuccess($testReports);
+ }
+
+ public function createTestReport(Request $request, $sprintId)
+ {
+ $user = auth('admins')->user();
+
+ $request->validate([
+ 'name' => 'required|string|max:255',
+ 'position' => 'required|integer',
+ 'input' => 'required|string',
+ 'expect_output' => 'required|string',
+ 'actual_output' => 'required|string',
+ 'note' => 'nullable|string',
+ 'issue_id_on_jira' => 'nullable|string',
+ 'bug_id_on_jira' => 'nullable|string',
+ ]);
+
+ $testReport = TestCaseForSprint::create([
+ 'name' => $request->name,
+ 'position' => $request->position,
+ 'input' => $request->input,
+ 'expect_output' => $request->expect_output,
+ 'actual_output' => $request->actual_output,
+ 'note' => $request->note,
+ 'issue_id_on_jira' => $request->issue_id_on_jira,
+ 'bug_id_on_jira' => $request->bug_id_on_jira,
+ 'sprint_id' => $sprintId,
+ 'created_by' => $user->name,
+ ]);
+ return AbstractController::ResultSuccess($testReport);
+ }
+
+ public function deleteTestReport(Request $request)
+ {
+ $id = $request->input('id');
+
+ $testReport = TestCaseForSprint::find($id);
+
+ if (!$testReport) {
+ return AbstractController::ResultError("Test report not found", [], 404);
+ }
+
+ $testReport->delete();
+ return AbstractController::ResultSuccess("Test report deleted successfully");
+ }
+}
diff --git a/BACKEND/Modules/Admin/app/Models/Criteria.php b/BACKEND/Modules/Admin/app/Models/Criteria.php
new file mode 100644
index 0000000..1824091
--- /dev/null
+++ b/BACKEND/Modules/Admin/app/Models/Criteria.php
@@ -0,0 +1,24 @@
+belongsToMany(Sprint::class, 'sprints_criterias')
+ ->withPivot('point', 'expect_result', 'actual_result', 'note');
+ }
+
+ public function users()
+ {
+ return $this->belongsToMany(User::class, 'users_criterias')
+ ->withPivot('point', 'note', 'sprint_id')
+ ;
+ }
+}
diff --git a/BACKEND/Modules/Admin/app/Models/Sprint.php b/BACKEND/Modules/Admin/app/Models/Sprint.php
new file mode 100644
index 0000000..2df4266
--- /dev/null
+++ b/BACKEND/Modules/Admin/app/Models/Sprint.php
@@ -0,0 +1,17 @@
+belongsToMany(Criteria::class, 'sprints_criterias')
+ ->withPivot('point', 'expect_result', 'actual_result', 'note');
+ }
+}
\ No newline at end of file
diff --git a/BACKEND/Modules/Admin/app/Models/SprintCriteria.php b/BACKEND/Modules/Admin/app/Models/SprintCriteria.php
new file mode 100644
index 0000000..5b1231a
--- /dev/null
+++ b/BACKEND/Modules/Admin/app/Models/SprintCriteria.php
@@ -0,0 +1,11 @@
+table = 'users';
+ $this->guarded = [];
+ $this->hidden = [
+ 'password',
+ 'forgot_code',
+ ];
+ }
+}
diff --git a/BACKEND/Modules/Admin/app/Models/UserCriteria.php b/BACKEND/Modules/Admin/app/Models/UserCriteria.php
new file mode 100644
index 0000000..10d3b49
--- /dev/null
+++ b/BACKEND/Modules/Admin/app/Models/UserCriteria.php
@@ -0,0 +1,11 @@
+middleware('check.permission:admin.staff');
Route::get('/allocation', [JiraController::class, 'getAllUserDoing'])->middleware('check.permission:admin.staff');
@@ -148,6 +155,20 @@ Route::middleware('api')
Route::get('/delete', [TicketController::class, 'deleteTicket'])->middleware('check.permission:admin.hr.staff');
Route::post('/handle-ticket', [TicketController::class, 'handleTicket'])->middleware('check.permission:admin');
});
+
+ Route::group([
+ 'prefix' => 'criterias',
+ ], function () {
+ Route::get('/sprints/{sprintId}', [CriteriasController::class, 'getCriteriasForSprint'])->middleware('check.permission:admin');
+ Route::get('/users/{userId}', [CriteriasController::class, 'getCriteriasForUser'])->middleware('check.permission:admin');
+ Route::get('/users/{userId}/sprints/{sprintId}', [CriteriasController::class, 'getCriteriasForUserBySprint'])->middleware('check.permission:admin');
+ Route::get('/getAll', [CriteriasController::class, 'getAllCriterias'])->middleware('check.permission:admin');
+ Route::post('/sprints/{sprintId}', [CriteriasController::class, 'updateCriteriasForSprint'])->middleware('check.permission:admin');
+
+ Route::get('/test-cases/getAll/{sprintId}', [TestCaseForSprintController::class, 'getAllReportsForSprint'])->middleware('check.permission:admin,tester');
+ Route::post('/test-cases/{sprintId}', [TestCaseForSprintController::class, 'createTestReport'])->middleware('check.permission:admin,tester');
+ Route::get('/test-cases/delete', [TestCaseForSprintController::class, 'deleteTestReport'])->middleware('check.permission:admin,tester');
+ });
});
});
diff --git a/BACKEND/app/Services/JiraService.php b/BACKEND/app/Services/JiraService.php
index 88d14bd..cc07d30 100644
--- a/BACKEND/app/Services/JiraService.php
+++ b/BACKEND/app/Services/JiraService.php
@@ -96,7 +96,7 @@ class JiraService
return $issues;
}
-
+
public function getAllUsers()
{
$response = $this->client->get('/rest/api/3/users/search', [
@@ -233,9 +233,9 @@ class JiraService
$user_warning = [];
foreach ($users as $user) {
$user = $user[0];
- $users_data[$user['displayName']]['user'] = $user;
- $users_data[$user['displayName']]['total_spent'] = 0;
- $users_data[$user['displayName']]['total_est'] = 0;
+ $users_data[$user['displayName']]['user'] = $user;
+ $users_data[$user['displayName']]['total_spent'] = 0;
+ $users_data[$user['displayName']]['total_est'] = 0;
$body = [
'expand' => ['names', 'schema'],
'fields' => ['summary', 'status', 'timeoriginalestimate', 'timespent', 'assignee', 'project', 'updated'],
@@ -246,21 +246,21 @@ class JiraService
'maxResults' => 50,
'startAt' => 0
];
-
+
$response = $this->client->post('/rest/api/3/search', [
'body' => json_encode($body)
]);
-
+
$issues = json_decode($response->getBody()->getContents(), true);
- if(count($issues['issues']) == 0){
+ if (count($issues['issues']) == 0) {
$user_warning[] = $user;
}
foreach ($issues['issues'] as $issue) {
$projectName = $issue['fields']['project']['name'];
$username = $issue['fields']['assignee']['displayName'];
-
+
if (!isset($groupedIssues[$projectName])) {
$groupedIssues[$projectName] = [];
$groupedIssues[$projectName]['project'] = $issue['fields']['project'];
@@ -272,19 +272,19 @@ class JiraService
$groupedIssues[$projectName]['users'][$username]['p_total_spent'] = 0;
$groupedIssues[$projectName]['users'][$username]['p_total_est'] = 0;
}
-
+
$groupedIssues[$projectName]['users'][$username]['issues'][] = $issue;
$groupedIssues[$projectName]['users'][$username]['p_total_spent'] = $groupedIssues[$projectName]['users'][$username]['p_total_spent'] + $issue['fields']['timespent'];
$groupedIssues[$projectName]['users'][$username]['p_total_est'] = $groupedIssues[$projectName]['users'][$username]['p_total_est'] + ($issue['fields']['timeoriginalestimate'] ?? 0);
-
+
$users_data[$user['displayName']]['total_spent'] = $users_data[$user['displayName']]['total_spent'] + $issue['fields']['timespent'];
- $users_data[$user['displayName']]['total_est'] = $users_data[$user['displayName']]['total_est'] + ($issue['fields']['timeoriginalestimate'] ?? 0);
+ $users_data[$user['displayName']]['total_est'] = $users_data[$user['displayName']]['total_est'] + ($issue['fields']['timeoriginalestimate'] ?? 0);
}
}
-
-
-
+
+
+
return ['projects' => $groupedIssues, 'users' => $users_data, 'warningList' => $user_warning];
// return $projects;
@@ -296,4 +296,24 @@ class JiraService
$issueDetail = json_decode($issueResponse->getBody()->getContents(), true);
return $issueDetail;
}
+ public function getDetailsProjectsById($id)
+ {
+ $response = $this->client->get('/rest/api/3/project/' . $id);
+ return json_decode($response->getBody()->getContents(), true);
+ }
+ public function getAllBoardByIdProjects($id)
+ {
+ $response = $this->client->get('/rest/agile/1.0/board?projectKeyOrI=/' . $id);
+ return json_decode($response->getBody()->getContents(), true);
+ }
+ public function getAllSprintByIdBoard($id)
+ {
+ $response = $this->client->get('/rest/agile/1.0/board/' . $id . '/sprint');
+ return json_decode($response->getBody()->getContents(), true);
+ }
+ public function getAllIssueByIdSprint($id)
+ {
+ $response = $this->client->get('/rest/agile/1.0/sprint/' . $id . '/issue');
+ return json_decode($response->getBody()->getContents(), true);
+ }
}
diff --git a/FRONTEND/src/components/Navbar/Navbar.tsx b/FRONTEND/src/components/Navbar/Navbar.tsx
index 8d522ac..a71c20a 100755
--- a/FRONTEND/src/components/Navbar/Navbar.tsx
+++ b/FRONTEND/src/components/Navbar/Navbar.tsx
@@ -31,7 +31,6 @@ import {
IconLogout,
// IconMail,
IconMoon,
- IconPasswordUser,
IconQrcode,
IconReport,
IconScan,
@@ -458,7 +457,7 @@ const Navbar = ({
Change mode
- setOpened(true)}>
+ {/* setOpened(true)}>
Change password
-
+ */}
+
@@ -165,7 +165,7 @@ const mainRoutes = [
{
path: '/test-report',
element: (
-
+
@@ -193,7 +193,7 @@ const mainRoutes = [
{
path: '/profile',
element: (
-
+