Spaces:
Configuration error
Configuration error
Continue report and user changes
Browse files- cookie.txt +5 -0
- response.txt +202 -0
- spring-boot.log +0 -0
- src/main/java/com/attendenceSystem/constant/Routes.java +8 -9
- src/main/java/com/attendenceSystem/constant/Views.java +5 -0
- src/main/java/com/attendenceSystem/module/attendance/dto/response/LeaveRequestResponse.java +1 -0
- src/main/java/com/attendenceSystem/module/attendance/mapper/response/LeaveRequestResponseMapper.java +3 -0
- src/main/java/com/attendenceSystem/module/report/api/ReportApiController.java +63 -0
- src/main/java/com/attendenceSystem/module/report/controller/ReportController.java +65 -0
- src/main/java/com/attendenceSystem/module/report/dto/request/CreateReportRequest.java +3 -1
- src/main/java/com/attendenceSystem/module/report/entity/ReportShare.java +39 -0
- src/main/java/com/attendenceSystem/module/report/repository/ReportShareRepository.java +18 -0
- src/main/java/com/attendenceSystem/module/report/service/ReportService.java +3 -1
- src/main/java/com/attendenceSystem/module/report/service/impl/ReportServiceImpl.java +29 -1
- src/main/java/com/attendenceSystem/module/user/controller/AccountController.java +24 -8
- src/main/java/com/attendenceSystem/module/user/controller/UserController.java +1 -1
- src/main/java/com/attendenceSystem/module/user/dto/request/UpdateUserInformationRequest.java +2 -2
- src/main/java/com/attendenceSystem/module/user/dto/response/UserInformationResponse.java +2 -0
- src/main/java/com/attendenceSystem/module/user/dto/response/UserResponse.java +12 -9
- src/main/java/com/attendenceSystem/module/user/entity/User.java +6 -2
- src/main/java/com/attendenceSystem/module/user/entity/converter/DepartmentConverter.java +13 -6
- src/main/java/com/attendenceSystem/module/user/entity/converter/SpecializationConverter.java +21 -0
- src/main/java/com/attendenceSystem/module/user/entity/enums/Department.java +7 -13
- src/main/java/com/attendenceSystem/module/user/entity/enums/Specialization.java +25 -0
- src/main/java/com/attendenceSystem/module/user/mapper/request/RegisterRequestMapper.java +2 -2
- src/main/java/com/attendenceSystem/module/user/mapper/response/UserInformationResponseMapper.java +1 -0
- src/main/java/com/attendenceSystem/module/user/mapper/response/UserResponseMapper.java +3 -0
- src/main/java/com/attendenceSystem/module/user/repository/UserRepository.java +4 -0
- src/main/java/com/attendenceSystem/module/user/service/AccountService.java +4 -1
- src/main/java/com/attendenceSystem/module/user/service/impl/AccountServiceImpl.java +39 -0
- src/main/java/com/attendenceSystem/module/user/service/impl/UserServiceImpl.java +2 -2
- src/main/java/com/attendenceSystem/security/CustomUserDetails.java +2 -2
- src/main/resources/static/css/account/employee-detail.css +195 -0
- src/main/resources/static/css/document/document-create.css +69 -1
- src/main/resources/templates/cms/account/employee-detail.html +127 -0
- src/main/resources/templates/cms/account/employee-list.html +16 -72
- src/main/resources/templates/cms/dashboard/dashboard-employee.html +1 -1
- src/main/resources/templates/cms/document/document-create.html +37 -16
- src/main/resources/templates/cms/document/document-list.html +74 -75
- src/main/resources/templates/cms/report/report-day.html +0 -144
- src/main/resources/templates/cms/report/report-month.html +0 -184
- src/main/resources/templates/cms/sidebar/sidebar-employee.html +3 -3
- src/main/resources/templates/cms/sidebar/sidebar-manage.html +1 -1
- src/main/resources/templates/cms/user/user-information.html +7 -7
cookie.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Netscape HTTP Cookie File
|
| 2 |
+
# https://curl.se/docs/http-cookies.html
|
| 3 |
+
# This file was generated by libcurl! Edit at your own risk.
|
| 4 |
+
|
| 5 |
+
#HttpOnly_localhost FALSE / FALSE 0 JSESSIONID 304BEE582413C60E27EA083347D13FC8
|
response.txt
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html>
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>Nộp báo cáo công việc</title>
|
| 8 |
+
|
| 9 |
+
<link rel="stylesheet" href="/css/document/document-create.css">
|
| 10 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
| 11 |
+
</head>
|
| 12 |
+
|
| 13 |
+
<body>
|
| 14 |
+
|
| 15 |
+
<div class="bg-image"></div>
|
| 16 |
+
|
| 17 |
+
<div class="layout">
|
| 18 |
+
|
| 19 |
+
<!-- SIDEBAR -->
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
<!-- CONTENT -->
|
| 27 |
+
<div class="container-layout">
|
| 28 |
+
|
| 29 |
+
<!-- HEADER -->
|
| 30 |
+
<div class="container-header">
|
| 31 |
+
<h1>Nộp báo cáo công việc</h1>
|
| 32 |
+
<p>Gửi báo cáo và chia sẻ cho phòng ban</p>
|
| 33 |
+
</div>
|
| 34 |
+
|
| 35 |
+
<main class="main-container">
|
| 36 |
+
|
| 37 |
+
<!-- FORM CARD -->
|
| 38 |
+
<section class="dashboard-card">
|
| 39 |
+
|
| 40 |
+
<div class="card-header">
|
| 41 |
+
<h2>Gửi báo cáo</h2>
|
| 42 |
+
</div>
|
| 43 |
+
|
| 44 |
+
<div class="card-body">
|
| 45 |
+
|
| 46 |
+
<form method="post" action="/report/create" enctype="multipart/form-data">
|
| 47 |
+
|
| 48 |
+
<!-- Tiêu đề -->
|
| 49 |
+
<div class="form-group">
|
| 50 |
+
<label>Tiêu đề báo cáo</label>
|
| 51 |
+
<input type="text" name="title" placeholder="Nhập tiêu đề báo cáo..." required>
|
| 52 |
+
</div>
|
| 53 |
+
|
| 54 |
+
<!-- Nội dung -->
|
| 55 |
+
<div class="form-group">
|
| 56 |
+
<label>Nội dung báo cáo</label>
|
| 57 |
+
<textarea name="content" rows="5" placeholder="Nhập nội dung báo cáo..."
|
| 58 |
+
required></textarea>
|
| 59 |
+
</div>
|
| 60 |
+
|
| 61 |
+
<!-- Phòng ban -->
|
| 62 |
+
<div class="form-group">
|
| 63 |
+
<label>Phòng ban</label>
|
| 64 |
+
|
| 65 |
+
<select name="departmentId" id="departmentSelect" required>
|
| 66 |
+
<option value="" disabled selected>Chọn phòng ban</option>
|
| 67 |
+
|
| 68 |
+
<option value="0">UNASSIGNED</option>
|
| 69 |
+
|
| 70 |
+
<option value="1">SE</option>
|
| 71 |
+
</select>
|
| 72 |
+
</div>
|
| 73 |
+
|
| 74 |
+
<!-- Người được xem -->
|
| 75 |
+
<div class="form-group">
|
| 76 |
+
<label>Người được chia sẻ</label>
|
| 77 |
+
|
| 78 |
+
<div id="userCheckboxes" class="checkbox-container">
|
| 79 |
+
<p class="placeholder-text">Chọn phòng ban trước</p>
|
| 80 |
+
</div>
|
| 81 |
+
</div>
|
| 82 |
+
|
| 83 |
+
<!-- Upload file -->
|
| 84 |
+
<div class="form-group">
|
| 85 |
+
<label>Tệp đính kèm</label>
|
| 86 |
+
|
| 87 |
+
<input type="file" name="files" multiple>
|
| 88 |
+
</div>
|
| 89 |
+
|
| 90 |
+
<!-- Link -->
|
| 91 |
+
<div class="form-group">
|
| 92 |
+
<label>Hoặc liên kết</label>
|
| 93 |
+
|
| 94 |
+
<input type="url" name="link" placeholder="https://drive.google.com/...">
|
| 95 |
+
</div>
|
| 96 |
+
|
| 97 |
+
<!-- BUTTON -->
|
| 98 |
+
<div class="form-actions">
|
| 99 |
+
|
| 100 |
+
<button type="submit" class="btn-primary">
|
| 101 |
+
<i class="fa-regular fa-paper-plane"></i>
|
| 102 |
+
Gửi báo cáo
|
| 103 |
+
</button>
|
| 104 |
+
|
| 105 |
+
<button type="reset" class="btn-secondary">
|
| 106 |
+
Reset
|
| 107 |
+
</button>
|
| 108 |
+
|
| 109 |
+
</div>
|
| 110 |
+
|
| 111 |
+
</form>
|
| 112 |
+
|
| 113 |
+
</div>
|
| 114 |
+
|
| 115 |
+
</section>
|
| 116 |
+
|
| 117 |
+
</main>
|
| 118 |
+
<footer>
|
| 119 |
+
<div class="footer-layout">
|
| 120 |
+
|
| 121 |
+
<div class="footer-left">
|
| 122 |
+
<h2 class="left-title">DXLab Attendance</h2>
|
| 123 |
+
|
| 124 |
+
<div class="left-description">
|
| 125 |
+
Hệ thống điểm danh thông minh bằng nhận diện khuôn mặt,
|
| 126 |
+
hỗ trợ quản lý sinh viên và giảng viên hiệu quả.
|
| 127 |
+
</div>
|
| 128 |
+
</div>
|
| 129 |
+
|
| 130 |
+
<div class="footer-right">
|
| 131 |
+
<div class="right-title">
|
| 132 |
+
Thông tin liên hệ
|
| 133 |
+
</div>
|
| 134 |
+
|
| 135 |
+
<a href="#" class="right-description">
|
| 136 |
+
Liên hệ với chúng tôi
|
| 137 |
+
</a>
|
| 138 |
+
|
| 139 |
+
<a href="#" class="right-description">
|
| 140 |
+
Báo cáo lỗi
|
| 141 |
+
</a>
|
| 142 |
+
|
| 143 |
+
<a href="#" class="right-description">
|
| 144 |
+
Điều khoản sử dụng
|
| 145 |
+
</a>
|
| 146 |
+
</div>
|
| 147 |
+
|
| 148 |
+
</div>
|
| 149 |
+
|
| 150 |
+
<div class="footer-bottom">
|
| 151 |
+
© 2026 DXLab Attendance. All rights reserved.
|
| 152 |
+
</div>
|
| 153 |
+
</footer>
|
| 154 |
+
|
| 155 |
+
</div>
|
| 156 |
+
</div>
|
| 157 |
+
|
| 158 |
+
<!-- FOOTER -->
|
| 159 |
+
|
| 160 |
+
<!-- JS load user theo phòng -->
|
| 161 |
+
<script>
|
| 162 |
+
document.getElementById("departmentSelect").addEventListener("change", function () {
|
| 163 |
+
|
| 164 |
+
const departmentId = this.value;
|
| 165 |
+
|
| 166 |
+
fetch(`/api/report/users/by-department/${departmentId}`)
|
| 167 |
+
.then(res => res.json())
|
| 168 |
+
.then(data => {
|
| 169 |
+
|
| 170 |
+
const container = document.getElementById("userCheckboxes");
|
| 171 |
+
container.innerHTML = "";
|
| 172 |
+
|
| 173 |
+
if (data.length === 0) {
|
| 174 |
+
container.innerHTML = "<p class='placeholder-text'>Không có người dùng trong phòng ban này</p>";
|
| 175 |
+
return;
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
data.forEach(user => {
|
| 179 |
+
const label = document.createElement("label");
|
| 180 |
+
label.className = "checkbox-label";
|
| 181 |
+
|
| 182 |
+
const checkbox = document.createElement("input");
|
| 183 |
+
checkbox.type = "checkbox";
|
| 184 |
+
checkbox.name = "sharedUserIds";
|
| 185 |
+
checkbox.value = user.id;
|
| 186 |
+
|
| 187 |
+
const span = document.createElement("span");
|
| 188 |
+
span.textContent = user.fullName;
|
| 189 |
+
|
| 190 |
+
label.appendChild(checkbox);
|
| 191 |
+
label.appendChild(span);
|
| 192 |
+
container.appendChild(label);
|
| 193 |
+
});
|
| 194 |
+
|
| 195 |
+
});
|
| 196 |
+
|
| 197 |
+
});
|
| 198 |
+
</script>
|
| 199 |
+
|
| 200 |
+
</body>
|
| 201 |
+
|
| 202 |
+
</html>
|
spring-boot.log
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/main/java/com/attendenceSystem/constant/Routes.java
CHANGED
|
@@ -32,18 +32,13 @@ public final class Routes {
|
|
| 32 |
public static final String PROFILE = "/profile";
|
| 33 |
public static final String CHANGE_PASSWORD = "/change-password";
|
| 34 |
public static final String UPDATE_PASSWORD = "/update-password";
|
| 35 |
-
public static final String
|
| 36 |
}
|
| 37 |
|
| 38 |
public static final class Account {
|
| 39 |
-
public static final String ROOT = "/
|
|
|
|
| 40 |
public static final String MANAGER = "/manager/accounts";
|
| 41 |
-
public static final String CREATE = "/create";
|
| 42 |
-
public static final String UPDATE = "/update";
|
| 43 |
-
public static final String DELETE = "/delete";
|
| 44 |
-
public static final String DEACTIVATE = "/deactivate";
|
| 45 |
-
public static final String ACTIVATE = "/activate";
|
| 46 |
-
public static final String UPDATE_INFORMATION = "/update-information";
|
| 47 |
}
|
| 48 |
|
| 49 |
public static final class Dashboard {
|
|
@@ -68,4 +63,8 @@ public final class Routes {
|
|
| 68 |
public static final class Schedule {
|
| 69 |
public static final String ROOT = "/schedule";
|
| 70 |
}
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
public static final String PROFILE = "/profile";
|
| 33 |
public static final String CHANGE_PASSWORD = "/change-password";
|
| 34 |
public static final String UPDATE_PASSWORD = "/update-password";
|
| 35 |
+
public static final String INFORMATION = "/information";
|
| 36 |
}
|
| 37 |
|
| 38 |
public static final class Account {
|
| 39 |
+
public static final String ROOT = "/accounts";
|
| 40 |
+
public static final String ADMIN = "/admin/accounts";
|
| 41 |
public static final String MANAGER = "/manager/accounts";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
}
|
| 43 |
|
| 44 |
public static final class Dashboard {
|
|
|
|
| 63 |
public static final class Schedule {
|
| 64 |
public static final String ROOT = "/schedule";
|
| 65 |
}
|
| 66 |
+
|
| 67 |
+
public static final class Report {
|
| 68 |
+
public static final String ROOT = "/report";
|
| 69 |
+
}
|
| 70 |
+
}
|
src/main/java/com/attendenceSystem/constant/Views.java
CHANGED
|
@@ -22,6 +22,7 @@ public final class Views {
|
|
| 22 |
public static final class Account {
|
| 23 |
public static final String LIST = "cms/account/account-list";
|
| 24 |
public static final String EMPLOYEE_LIST = "cms/account/employee-list";
|
|
|
|
| 25 |
public static final String DETAIL = "cms/account/account-detail";
|
| 26 |
}
|
| 27 |
|
|
@@ -43,4 +44,8 @@ public final class Views {
|
|
| 43 |
public static final String CREATE = "cms/face-id/faceID-create";
|
| 44 |
public static final String LIST = "cms/face-id/faceID-list";
|
| 45 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
}
|
|
|
|
| 22 |
public static final class Account {
|
| 23 |
public static final String LIST = "cms/account/account-list";
|
| 24 |
public static final String EMPLOYEE_LIST = "cms/account/employee-list";
|
| 25 |
+
public static final String EMPLOYEE_DETAIL = "cms/account/employee-detail";
|
| 26 |
public static final String DETAIL = "cms/account/account-detail";
|
| 27 |
}
|
| 28 |
|
|
|
|
| 44 |
public static final String CREATE = "cms/face-id/faceID-create";
|
| 45 |
public static final String LIST = "cms/face-id/faceID-list";
|
| 46 |
}
|
| 47 |
+
public static final class Document {
|
| 48 |
+
public static final String CREATE = "cms/document/document-create";
|
| 49 |
+
public static final String LIST = "cms/document/document-list";
|
| 50 |
+
}
|
| 51 |
}
|
src/main/java/com/attendenceSystem/module/attendance/dto/response/LeaveRequestResponse.java
CHANGED
|
@@ -8,6 +8,7 @@ import com.attendenceSystem.module.attendance.entity.enums.LeaveStatus;
|
|
| 8 |
public record LeaveRequestResponse(
|
| 9 |
Long id,
|
| 10 |
String fullName,
|
|
|
|
| 11 |
LocalDate startDate,
|
| 12 |
LocalDate endDate,
|
| 13 |
String reason,
|
|
|
|
| 8 |
public record LeaveRequestResponse(
|
| 9 |
Long id,
|
| 10 |
String fullName,
|
| 11 |
+
String departmentDisplay,
|
| 12 |
LocalDate startDate,
|
| 13 |
LocalDate endDate,
|
| 14 |
String reason,
|
src/main/java/com/attendenceSystem/module/attendance/mapper/response/LeaveRequestResponseMapper.java
CHANGED
|
@@ -15,6 +15,9 @@ public class LeaveRequestResponseMapper {
|
|
| 15 |
return new LeaveRequestResponse(
|
| 16 |
leave.getId(),
|
| 17 |
leave.getUser() == null ? null : leave.getUser().getFullName(),
|
|
|
|
|
|
|
|
|
|
| 18 |
leave.getStartDate(),
|
| 19 |
leave.getEndDate(),
|
| 20 |
leave.getReason(),
|
|
|
|
| 15 |
return new LeaveRequestResponse(
|
| 16 |
leave.getId(),
|
| 17 |
leave.getUser() == null ? null : leave.getUser().getFullName(),
|
| 18 |
+
leave.getUser() == null || leave.getUser().getDepartment() == null
|
| 19 |
+
? "N/A"
|
| 20 |
+
: leave.getUser().getDepartment().getDisplayName(),
|
| 21 |
leave.getStartDate(),
|
| 22 |
leave.getEndDate(),
|
| 23 |
leave.getReason(),
|
src/main/java/com/attendenceSystem/module/report/api/ReportApiController.java
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.attendenceSystem.module.report.api;
|
| 2 |
+
|
| 3 |
+
import org.springframework.data.domain.Page;
|
| 4 |
+
import org.springframework.data.domain.Pageable;
|
| 5 |
+
import org.springframework.http.ResponseEntity;
|
| 6 |
+
import org.springframework.web.bind.annotation.GetMapping;
|
| 7 |
+
import org.springframework.web.bind.annotation.PathVariable;
|
| 8 |
+
import org.springframework.web.bind.annotation.RequestMapping;
|
| 9 |
+
import org.springframework.web.bind.annotation.RestController;
|
| 10 |
+
|
| 11 |
+
import com.attendenceSystem.constant.Routes;
|
| 12 |
+
import com.attendenceSystem.module.report.dto.response.ReportDetailResponse;
|
| 13 |
+
import com.attendenceSystem.module.report.dto.response.ReportResponse;
|
| 14 |
+
import com.attendenceSystem.module.report.service.ReportService;
|
| 15 |
+
import com.attendenceSystem.module.user.entity.User;
|
| 16 |
+
import com.attendenceSystem.module.user.entity.enums.Specialization;
|
| 17 |
+
import com.attendenceSystem.module.user.entity.enums.Role;
|
| 18 |
+
import com.attendenceSystem.module.user.repository.UserRepository;
|
| 19 |
+
|
| 20 |
+
import lombok.RequiredArgsConstructor;
|
| 21 |
+
|
| 22 |
+
import java.util.List;
|
| 23 |
+
import java.util.stream.Collectors;
|
| 24 |
+
|
| 25 |
+
@RestController
|
| 26 |
+
@RequestMapping(Routes.API + Routes.Report.ROOT)
|
| 27 |
+
@RequiredArgsConstructor
|
| 28 |
+
public class ReportApiController {
|
| 29 |
+
|
| 30 |
+
private final ReportService reportService;
|
| 31 |
+
private final UserRepository userRepository;
|
| 32 |
+
|
| 33 |
+
@GetMapping
|
| 34 |
+
public ResponseEntity<Page<ReportResponse>> getAllReports(Pageable pageable) {
|
| 35 |
+
Page<ReportResponse> reports = reportService.getAllReports(pageable);
|
| 36 |
+
return ResponseEntity.ok(reports);
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
@GetMapping("/my-reports")
|
| 40 |
+
public ResponseEntity<Page<ReportResponse>> getMyReports(Pageable pageable) {
|
| 41 |
+
Page<ReportResponse> reports = reportService.getMyReports(pageable);
|
| 42 |
+
return ResponseEntity.ok(reports);
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
@GetMapping("/shared-with-me")
|
| 46 |
+
public ResponseEntity<Page<ReportResponse>> getSharedWithMe(Pageable pageable) {
|
| 47 |
+
Page<ReportResponse> reports = reportService.getSharedWithMe(pageable);
|
| 48 |
+
return ResponseEntity.ok(reports);
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
@GetMapping("/{id}")
|
| 52 |
+
public ResponseEntity<ReportDetailResponse> getReportById(@PathVariable Long id) {
|
| 53 |
+
ReportDetailResponse report = reportService.getReportById(id);
|
| 54 |
+
return ResponseEntity.ok(report);
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
@GetMapping("/users/by-department/{departmentId}")
|
| 58 |
+
public ResponseEntity<List<User>> getUsersByDepartment(@PathVariable Integer departmentId) {
|
| 59 |
+
Specialization specialization = Specialization.fromValue(departmentId);
|
| 60 |
+
List<User> users = userRepository.findBySpecializationAndRoleNot(specialization, Role.ADMIN);
|
| 61 |
+
return ResponseEntity.ok(users);
|
| 62 |
+
}
|
| 63 |
+
}
|
src/main/java/com/attendenceSystem/module/report/controller/ReportController.java
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.attendenceSystem.module.report.controller;
|
| 2 |
+
|
| 3 |
+
import jakarta.servlet.http.HttpSession;
|
| 4 |
+
import org.springframework.stereotype.Controller;
|
| 5 |
+
import org.springframework.web.bind.annotation.GetMapping;
|
| 6 |
+
import org.springframework.web.bind.annotation.PostMapping;
|
| 7 |
+
import org.springframework.web.bind.annotation.RequestMapping;
|
| 8 |
+
import org.springframework.web.bind.annotation.RequestParam;
|
| 9 |
+
import org.springframework.web.multipart.MultipartFile;
|
| 10 |
+
|
| 11 |
+
import com.attendenceSystem.constant.Routes;
|
| 12 |
+
import com.attendenceSystem.constant.Views;
|
| 13 |
+
import com.attendenceSystem.module.report.dto.request.CreateReportRequest;
|
| 14 |
+
import com.attendenceSystem.module.report.service.ReportService;
|
| 15 |
+
import com.attendenceSystem.module.user.entity.enums.Specialization;
|
| 16 |
+
|
| 17 |
+
import lombok.RequiredArgsConstructor;
|
| 18 |
+
import org.springframework.ui.Model;
|
| 19 |
+
|
| 20 |
+
@Controller
|
| 21 |
+
@RequestMapping(Routes.Report.ROOT)
|
| 22 |
+
@RequiredArgsConstructor
|
| 23 |
+
public class ReportController {
|
| 24 |
+
|
| 25 |
+
private final ReportService reportService;
|
| 26 |
+
|
| 27 |
+
@GetMapping
|
| 28 |
+
public String reportList() {
|
| 29 |
+
return Views.Document.LIST;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
@GetMapping(Routes.Action.CREATE)
|
| 33 |
+
public String reportCreate(Model model) {
|
| 34 |
+
model.addAttribute("departments", Specialization.values());
|
| 35 |
+
return Views.Document.CREATE;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
@PostMapping(Routes.Action.CREATE)
|
| 39 |
+
public String submitReport(
|
| 40 |
+
@RequestParam("title") String title,
|
| 41 |
+
@RequestParam("content") String content,
|
| 42 |
+
@RequestParam("departmentId") Integer departmentId,
|
| 43 |
+
@RequestParam(value = "sharedUserIds", required = false) Long[] sharedUserIds,
|
| 44 |
+
@RequestParam(value = "files", required = false) MultipartFile[] files,
|
| 45 |
+
@RequestParam(value = "link", required = false) String link,
|
| 46 |
+
HttpSession session,
|
| 47 |
+
Model model) {
|
| 48 |
+
|
| 49 |
+
try {
|
| 50 |
+
CreateReportRequest request = new CreateReportRequest();
|
| 51 |
+
request.setTitle(title);
|
| 52 |
+
request.setContent(content);
|
| 53 |
+
request.setAttachmentUrl(link);
|
| 54 |
+
request.setSharedUserIds(sharedUserIds);
|
| 55 |
+
|
| 56 |
+
reportService.createReport(request);
|
| 57 |
+
|
| 58 |
+
return Routes.REDIRECT + Routes.Report.ROOT + "?success=true";
|
| 59 |
+
} catch (Exception e) {
|
| 60 |
+
model.addAttribute("errorMessage", e.getMessage());
|
| 61 |
+
model.addAttribute("departments", Specialization.values());
|
| 62 |
+
return Views.Document.CREATE;
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
}
|
src/main/java/com/attendenceSystem/module/report/dto/request/CreateReportRequest.java
CHANGED
|
@@ -22,4 +22,6 @@ public class CreateReportRequest {
|
|
| 22 |
private String content;
|
| 23 |
|
| 24 |
private String attachmentUrl;
|
| 25 |
-
|
|
|
|
|
|
|
|
|
| 22 |
private String content;
|
| 23 |
|
| 24 |
private String attachmentUrl;
|
| 25 |
+
|
| 26 |
+
private Long[] sharedUserIds;
|
| 27 |
+
}
|
src/main/java/com/attendenceSystem/module/report/entity/ReportShare.java
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.attendenceSystem.module.report.entity;
|
| 2 |
+
|
| 3 |
+
import com.attendenceSystem.module.user.entity.User;
|
| 4 |
+
|
| 5 |
+
import jakarta.persistence.Entity;
|
| 6 |
+
import jakarta.persistence.FetchType;
|
| 7 |
+
import jakarta.persistence.GeneratedValue;
|
| 8 |
+
import jakarta.persistence.GenerationType;
|
| 9 |
+
import jakarta.persistence.Id;
|
| 10 |
+
import jakarta.persistence.JoinColumn;
|
| 11 |
+
import jakarta.persistence.ManyToOne;
|
| 12 |
+
import jakarta.persistence.Table;
|
| 13 |
+
import lombok.AllArgsConstructor;
|
| 14 |
+
import lombok.Builder;
|
| 15 |
+
import lombok.Getter;
|
| 16 |
+
import lombok.NoArgsConstructor;
|
| 17 |
+
import lombok.Setter;
|
| 18 |
+
|
| 19 |
+
@Getter
|
| 20 |
+
@Setter
|
| 21 |
+
@NoArgsConstructor
|
| 22 |
+
@AllArgsConstructor
|
| 23 |
+
@Builder
|
| 24 |
+
@Entity
|
| 25 |
+
@Table(name = "report_shares")
|
| 26 |
+
public class ReportShare {
|
| 27 |
+
|
| 28 |
+
@Id
|
| 29 |
+
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
| 30 |
+
private Long id;
|
| 31 |
+
|
| 32 |
+
@ManyToOne(fetch = FetchType.LAZY)
|
| 33 |
+
@JoinColumn(name = "report_id", nullable = false)
|
| 34 |
+
private Report report;
|
| 35 |
+
|
| 36 |
+
@ManyToOne(fetch = FetchType.LAZY)
|
| 37 |
+
@JoinColumn(name = "user_id", nullable = false)
|
| 38 |
+
private User user;
|
| 39 |
+
}
|
src/main/java/com/attendenceSystem/module/report/repository/ReportShareRepository.java
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.attendenceSystem.module.report.repository;
|
| 2 |
+
|
| 3 |
+
import org.springframework.data.domain.Page;
|
| 4 |
+
import org.springframework.data.domain.Pageable;
|
| 5 |
+
import org.springframework.data.jpa.repository.JpaRepository;
|
| 6 |
+
import org.springframework.data.jpa.repository.Query;
|
| 7 |
+
import org.springframework.stereotype.Repository;
|
| 8 |
+
|
| 9 |
+
import com.attendenceSystem.module.report.entity.Report;
|
| 10 |
+
import com.attendenceSystem.module.report.entity.ReportShare;
|
| 11 |
+
import com.attendenceSystem.module.user.entity.User;
|
| 12 |
+
|
| 13 |
+
@Repository
|
| 14 |
+
public interface ReportShareRepository extends JpaRepository<ReportShare, Long> {
|
| 15 |
+
|
| 16 |
+
@Query("SELECT rs.report FROM ReportShare rs WHERE rs.user = :user ORDER BY rs.report.createdAt DESC")
|
| 17 |
+
Page<Report> findReportsSharedWithUser(User user, Pageable pageable);
|
| 18 |
+
}
|
src/main/java/com/attendenceSystem/module/report/service/ReportService.java
CHANGED
|
@@ -15,5 +15,7 @@ public interface ReportService {
|
|
| 15 |
|
| 16 |
Page<ReportResponse> getMyReports(Pageable pageable);
|
| 17 |
|
|
|
|
|
|
|
| 18 |
ReportDetailResponse getReportById(Long id);
|
| 19 |
-
}
|
|
|
|
| 15 |
|
| 16 |
Page<ReportResponse> getMyReports(Pageable pageable);
|
| 17 |
|
| 18 |
+
Page<ReportResponse> getSharedWithMe(Pageable pageable);
|
| 19 |
+
|
| 20 |
ReportDetailResponse getReportById(Long id);
|
| 21 |
+
}
|
src/main/java/com/attendenceSystem/module/report/service/impl/ReportServiceImpl.java
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
package com.attendenceSystem.module.report.service.impl;
|
| 2 |
|
| 3 |
import java.time.Instant;
|
|
|
|
|
|
|
| 4 |
|
| 5 |
import org.springframework.data.domain.Page;
|
| 6 |
import org.springframework.data.domain.Pageable;
|
|
@@ -12,10 +14,12 @@ import com.attendenceSystem.module.report.dto.request.CreateReportRequest;
|
|
| 12 |
import com.attendenceSystem.module.report.dto.response.ReportDetailResponse;
|
| 13 |
import com.attendenceSystem.module.report.dto.response.ReportResponse;
|
| 14 |
import com.attendenceSystem.module.report.entity.Report;
|
|
|
|
| 15 |
import com.attendenceSystem.module.report.mapper.request.CreateReportRequestMapper;
|
| 16 |
import com.attendenceSystem.module.report.mapper.response.ReportDetailResponseMapper;
|
| 17 |
import com.attendenceSystem.module.report.mapper.response.ReportResponseMapper;
|
| 18 |
import com.attendenceSystem.module.report.repository.ReportRepository;
|
|
|
|
| 19 |
import com.attendenceSystem.module.report.service.ReportService;
|
| 20 |
import com.attendenceSystem.module.user.entity.User;
|
| 21 |
import com.attendenceSystem.module.user.repository.UserRepository;
|
|
@@ -28,6 +32,7 @@ import lombok.RequiredArgsConstructor;
|
|
| 28 |
public class ReportServiceImpl implements ReportService {
|
| 29 |
|
| 30 |
private final ReportRepository reportRepository;
|
|
|
|
| 31 |
private final UserRepository userRepository;
|
| 32 |
|
| 33 |
@Transactional
|
|
@@ -39,6 +44,22 @@ public class ReportServiceImpl implements ReportService {
|
|
| 39 |
report.setCreatedAt(Instant.now());
|
| 40 |
|
| 41 |
Report savedReport = reportRepository.save(report);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
return ReportResponseMapper.fromEntity(savedReport);
|
| 43 |
}
|
| 44 |
|
|
@@ -55,6 +76,13 @@ public class ReportServiceImpl implements ReportService {
|
|
| 55 |
.map(ReportResponseMapper::fromEntity);
|
| 56 |
}
|
| 57 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
@Override
|
| 59 |
public ReportDetailResponse getReportById(final Long id) {
|
| 60 |
Report report = reportRepository.findById(id)
|
|
@@ -71,4 +99,4 @@ public class ReportServiceImpl implements ReportService {
|
|
| 71 |
.orElseThrow(() -> new IllegalArgumentException(
|
| 72 |
"Không tìm thấy người dùng với tên đăng nhập: " + currentUsername));
|
| 73 |
}
|
| 74 |
-
}
|
|
|
|
| 1 |
package com.attendenceSystem.module.report.service.impl;
|
| 2 |
|
| 3 |
import java.time.Instant;
|
| 4 |
+
import java.util.ArrayList;
|
| 5 |
+
import java.util.List;
|
| 6 |
|
| 7 |
import org.springframework.data.domain.Page;
|
| 8 |
import org.springframework.data.domain.Pageable;
|
|
|
|
| 14 |
import com.attendenceSystem.module.report.dto.response.ReportDetailResponse;
|
| 15 |
import com.attendenceSystem.module.report.dto.response.ReportResponse;
|
| 16 |
import com.attendenceSystem.module.report.entity.Report;
|
| 17 |
+
import com.attendenceSystem.module.report.entity.ReportShare;
|
| 18 |
import com.attendenceSystem.module.report.mapper.request.CreateReportRequestMapper;
|
| 19 |
import com.attendenceSystem.module.report.mapper.response.ReportDetailResponseMapper;
|
| 20 |
import com.attendenceSystem.module.report.mapper.response.ReportResponseMapper;
|
| 21 |
import com.attendenceSystem.module.report.repository.ReportRepository;
|
| 22 |
+
import com.attendenceSystem.module.report.repository.ReportShareRepository;
|
| 23 |
import com.attendenceSystem.module.report.service.ReportService;
|
| 24 |
import com.attendenceSystem.module.user.entity.User;
|
| 25 |
import com.attendenceSystem.module.user.repository.UserRepository;
|
|
|
|
| 32 |
public class ReportServiceImpl implements ReportService {
|
| 33 |
|
| 34 |
private final ReportRepository reportRepository;
|
| 35 |
+
private final ReportShareRepository reportShareRepository;
|
| 36 |
private final UserRepository userRepository;
|
| 37 |
|
| 38 |
@Transactional
|
|
|
|
| 44 |
report.setCreatedAt(Instant.now());
|
| 45 |
|
| 46 |
Report savedReport = reportRepository.save(report);
|
| 47 |
+
|
| 48 |
+
// Save shared users
|
| 49 |
+
if (request.getSharedUserIds() != null && request.getSharedUserIds().length > 0) {
|
| 50 |
+
List<ReportShare> shares = new ArrayList<>();
|
| 51 |
+
for (Long userId : request.getSharedUserIds()) {
|
| 52 |
+
User sharedUser = userRepository.findById(userId)
|
| 53 |
+
.orElseThrow(() -> new IllegalArgumentException(
|
| 54 |
+
"Không tìm thấy người dùng với id: " + userId));
|
| 55 |
+
shares.add(ReportShare.builder()
|
| 56 |
+
.report(savedReport)
|
| 57 |
+
.user(sharedUser)
|
| 58 |
+
.build());
|
| 59 |
+
}
|
| 60 |
+
reportShareRepository.saveAll(shares);
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
return ReportResponseMapper.fromEntity(savedReport);
|
| 64 |
}
|
| 65 |
|
|
|
|
| 76 |
.map(ReportResponseMapper::fromEntity);
|
| 77 |
}
|
| 78 |
|
| 79 |
+
@Override
|
| 80 |
+
public Page<ReportResponse> getSharedWithMe(final Pageable pageable) {
|
| 81 |
+
User currentUser = findCurrentUser();
|
| 82 |
+
return reportShareRepository.findReportsSharedWithUser(currentUser, pageable)
|
| 83 |
+
.map(ReportResponseMapper::fromEntity);
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
@Override
|
| 87 |
public ReportDetailResponse getReportById(final Long id) {
|
| 88 |
Report report = reportRepository.findById(id)
|
|
|
|
| 99 |
.orElseThrow(() -> new IllegalArgumentException(
|
| 100 |
"Không tìm thấy người dùng với tên đăng nhập: " + currentUsername));
|
| 101 |
}
|
| 102 |
+
}
|
src/main/java/com/attendenceSystem/module/user/controller/AccountController.java
CHANGED
|
@@ -22,7 +22,7 @@ import org.springframework.web.bind.annotation.PostMapping;
|
|
| 22 |
public class AccountController {
|
| 23 |
private final AccountService accountService;
|
| 24 |
|
| 25 |
-
@GetMapping(Routes.Account.
|
| 26 |
public String toListPage(@PageableDefault(size = 10) Pageable pageable, Model model) {
|
| 27 |
model.addAttribute("users", accountService.getUsers(pageable));
|
| 28 |
model.addAttribute("currentUsername", SecurityUtil.getCurrentUserName());
|
|
@@ -32,28 +32,44 @@ public class AccountController {
|
|
| 32 |
|
| 33 |
@GetMapping(Routes.Account.MANAGER)
|
| 34 |
public String toEmployeeListPage(@PageableDefault(size = 10) Pageable pageable, Model model) {
|
| 35 |
-
model.addAttribute("users", accountService.
|
| 36 |
model.addAttribute("currentUsername", SecurityUtil.getCurrentUserName());
|
| 37 |
model.addAttribute("currentRole", SecurityUtil.getCurrentUserRole());
|
|
|
|
| 38 |
return Views.Account.EMPLOYEE_LIST;
|
| 39 |
}
|
| 40 |
|
| 41 |
-
@GetMapping(Routes.Account.
|
| 42 |
public String toDetailPage(@PathVariable("id") Long id, Model model) {
|
| 43 |
UserResponse user = accountService.getUserById(id);
|
| 44 |
model.addAttribute("user", user);
|
| 45 |
return Views.User.DETAIL;
|
| 46 |
}
|
| 47 |
|
| 48 |
-
@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
public String deactiveUser(@PathVariable("id") Long id) {
|
| 50 |
accountService.deactivateUser(id);
|
| 51 |
-
return Routes.REDIRECT + Routes.Account.
|
| 52 |
}
|
| 53 |
|
| 54 |
-
@PostMapping(Routes.Account.
|
| 55 |
public String activateUser(@PathVariable("id") Long id) {
|
| 56 |
accountService.activateUser(id);
|
| 57 |
-
return Routes.REDIRECT + Routes.Account.
|
| 58 |
}
|
| 59 |
-
}
|
|
|
|
| 22 |
public class AccountController {
|
| 23 |
private final AccountService accountService;
|
| 24 |
|
| 25 |
+
@GetMapping(Routes.Account.ADMIN)
|
| 26 |
public String toListPage(@PageableDefault(size = 10) Pageable pageable, Model model) {
|
| 27 |
model.addAttribute("users", accountService.getUsers(pageable));
|
| 28 |
model.addAttribute("currentUsername", SecurityUtil.getCurrentUserName());
|
|
|
|
| 32 |
|
| 33 |
@GetMapping(Routes.Account.MANAGER)
|
| 34 |
public String toEmployeeListPage(@PageableDefault(size = 10) Pageable pageable, Model model) {
|
| 35 |
+
model.addAttribute("users", accountService.getEmployees(pageable));
|
| 36 |
model.addAttribute("currentUsername", SecurityUtil.getCurrentUserName());
|
| 37 |
model.addAttribute("currentRole", SecurityUtil.getCurrentUserRole());
|
| 38 |
+
model.addAttribute("departments", com.attendenceSystem.module.user.entity.enums.Department.values());
|
| 39 |
return Views.Account.EMPLOYEE_LIST;
|
| 40 |
}
|
| 41 |
|
| 42 |
+
@GetMapping(Routes.Account.ADMIN + "/{id}")
|
| 43 |
public String toDetailPage(@PathVariable("id") Long id, Model model) {
|
| 44 |
UserResponse user = accountService.getUserById(id);
|
| 45 |
model.addAttribute("user", user);
|
| 46 |
return Views.User.DETAIL;
|
| 47 |
}
|
| 48 |
|
| 49 |
+
@GetMapping(Routes.Account.MANAGER + "/{id}")
|
| 50 |
+
public String toEmployeeDetailPage(@PathVariable("id") Long id, Model model) {
|
| 51 |
+
UserResponse user = accountService.getUserById(id);
|
| 52 |
+
model.addAttribute("user", user);
|
| 53 |
+
model.addAttribute("departments", com.attendenceSystem.module.user.entity.enums.Department.values());
|
| 54 |
+
return Views.Account.EMPLOYEE_DETAIL;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
@PostMapping(Routes.Account.MANAGER + Routes.Action.UPDATE + "/{id}")
|
| 58 |
+
public String changeEmployeeDepartment(@PathVariable("id") Long id,
|
| 59 |
+
@org.springframework.web.bind.annotation.RequestParam("department") String department) {
|
| 60 |
+
accountService.changeDepartment(id, department);
|
| 61 |
+
return Routes.REDIRECT + Routes.Account.MANAGER;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
@PostMapping(Routes.Account.ADMIN + Routes.Action.DEACTIVATE + "/{id}")
|
| 65 |
public String deactiveUser(@PathVariable("id") Long id) {
|
| 66 |
accountService.deactivateUser(id);
|
| 67 |
+
return Routes.REDIRECT + Routes.Account.ADMIN;
|
| 68 |
}
|
| 69 |
|
| 70 |
+
@PostMapping(Routes.Account.ADMIN + Routes.Action.ACTIVATE + "/{id}")
|
| 71 |
public String activateUser(@PathVariable("id") Long id) {
|
| 72 |
accountService.activateUser(id);
|
| 73 |
+
return Routes.REDIRECT + Routes.Account.ADMIN;
|
| 74 |
}
|
| 75 |
+
}
|
src/main/java/com/attendenceSystem/module/user/controller/UserController.java
CHANGED
|
@@ -100,7 +100,7 @@ public class UserController {
|
|
| 100 |
}
|
| 101 |
}
|
| 102 |
|
| 103 |
-
@PostMapping(Routes.User.
|
| 104 |
public String updateUserInformation(@Valid @ModelAttribute UpdateUserInformationRequest request,
|
| 105 |
BindingResult result,
|
| 106 |
Model model) {
|
|
|
|
| 100 |
}
|
| 101 |
}
|
| 102 |
|
| 103 |
+
@PostMapping(Routes.User.INFORMATION + Routes.Action.UPDATE)
|
| 104 |
public String updateUserInformation(@Valid @ModelAttribute UpdateUserInformationRequest request,
|
| 105 |
BindingResult result,
|
| 106 |
Model model) {
|
src/main/java/com/attendenceSystem/module/user/dto/request/UpdateUserInformationRequest.java
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
package com.attendenceSystem.module.user.dto.request;
|
| 2 |
|
| 3 |
-
import com.attendenceSystem.module.user.entity.enums.
|
| 4 |
|
| 5 |
import jakarta.validation.constraints.Email;
|
| 6 |
import jakarta.validation.constraints.NotBlank;
|
|
@@ -23,5 +23,5 @@ public class UpdateUserInformationRequest {
|
|
| 23 |
@Size(max = 255, message = "Email không được vượt quá 255 ký tự")
|
| 24 |
private String email;
|
| 25 |
|
| 26 |
-
private
|
| 27 |
}
|
|
|
|
| 1 |
package com.attendenceSystem.module.user.dto.request;
|
| 2 |
|
| 3 |
+
import com.attendenceSystem.module.user.entity.enums.Specialization;
|
| 4 |
|
| 5 |
import jakarta.validation.constraints.Email;
|
| 6 |
import jakarta.validation.constraints.NotBlank;
|
|
|
|
| 23 |
@Size(max = 255, message = "Email không được vượt quá 255 ký tự")
|
| 24 |
private String email;
|
| 25 |
|
| 26 |
+
private Specialization specialization;
|
| 27 |
}
|
src/main/java/com/attendenceSystem/module/user/dto/response/UserInformationResponse.java
CHANGED
|
@@ -2,6 +2,7 @@ package com.attendenceSystem.module.user.dto.response;
|
|
| 2 |
|
| 3 |
import com.attendenceSystem.module.user.entity.enums.Department;
|
| 4 |
import com.attendenceSystem.module.user.entity.enums.Role;
|
|
|
|
| 5 |
import com.attendenceSystem.module.user.entity.enums.Status;
|
| 6 |
|
| 7 |
import lombok.Builder;
|
|
@@ -15,6 +16,7 @@ public record UserInformationResponse(
|
|
| 15 |
String fullName,
|
| 16 |
Role role,
|
| 17 |
Status status,
|
|
|
|
| 18 |
Department department) {
|
| 19 |
|
| 20 |
}
|
|
|
|
| 2 |
|
| 3 |
import com.attendenceSystem.module.user.entity.enums.Department;
|
| 4 |
import com.attendenceSystem.module.user.entity.enums.Role;
|
| 5 |
+
import com.attendenceSystem.module.user.entity.enums.Specialization;
|
| 6 |
import com.attendenceSystem.module.user.entity.enums.Status;
|
| 7 |
|
| 8 |
import lombok.Builder;
|
|
|
|
| 16 |
String fullName,
|
| 17 |
Role role,
|
| 18 |
Status status,
|
| 19 |
+
Specialization specialization,
|
| 20 |
Department department) {
|
| 21 |
|
| 22 |
}
|
src/main/java/com/attendenceSystem/module/user/dto/response/UserResponse.java
CHANGED
|
@@ -2,13 +2,16 @@ package com.attendenceSystem.module.user.dto.response;
|
|
| 2 |
|
| 3 |
import lombok.Builder;
|
| 4 |
|
| 5 |
-
@Builder
|
| 6 |
-
public record UserResponse(
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
-
}
|
|
|
|
| 2 |
|
| 3 |
import lombok.Builder;
|
| 4 |
|
| 5 |
+
@Builder
|
| 6 |
+
public record UserResponse(
|
| 7 |
+
Long id,
|
| 8 |
+
String username,
|
| 9 |
+
String email,
|
| 10 |
+
String phone,
|
| 11 |
+
String fullName,
|
| 12 |
+
String status,
|
| 13 |
+
String role,
|
| 14 |
+
String department,
|
| 15 |
+
String departmentDisplay) {
|
| 16 |
|
| 17 |
+
}
|
src/main/java/com/attendenceSystem/module/user/entity/User.java
CHANGED
|
@@ -7,6 +7,7 @@ import org.hibernate.annotations.UpdateTimestamp;
|
|
| 7 |
|
| 8 |
import com.attendenceSystem.module.user.entity.enums.Department;
|
| 9 |
import com.attendenceSystem.module.user.entity.enums.Role;
|
|
|
|
| 10 |
import com.attendenceSystem.module.user.entity.enums.Status;
|
| 11 |
|
| 12 |
import jakarta.persistence.Column;
|
|
@@ -48,7 +49,10 @@ public class User {
|
|
| 48 |
private Status status = Status.PENDING;
|
| 49 |
@Column(name = "full_name", nullable = false, length = 255)
|
| 50 |
private String fullName;
|
| 51 |
-
@Column(name = "
|
|
|
|
|
|
|
|
|
|
| 52 |
private Department department;
|
| 53 |
@CreationTimestamp
|
| 54 |
@Column(name = "created_at", nullable = false, updatable = false)
|
|
@@ -59,4 +63,4 @@ public class User {
|
|
| 59 |
@Column(name = "must_change_password", nullable = false)
|
| 60 |
@Builder.Default
|
| 61 |
private boolean mustChangePassword = false;
|
| 62 |
-
}
|
|
|
|
| 7 |
|
| 8 |
import com.attendenceSystem.module.user.entity.enums.Department;
|
| 9 |
import com.attendenceSystem.module.user.entity.enums.Role;
|
| 10 |
+
import com.attendenceSystem.module.user.entity.enums.Specialization;
|
| 11 |
import com.attendenceSystem.module.user.entity.enums.Status;
|
| 12 |
|
| 13 |
import jakarta.persistence.Column;
|
|
|
|
| 49 |
private Status status = Status.PENDING;
|
| 50 |
@Column(name = "full_name", nullable = false, length = 255)
|
| 51 |
private String fullName;
|
| 52 |
+
@Column(name = "specialization", nullable = false)
|
| 53 |
+
@Builder.Default
|
| 54 |
+
private Specialization specialization = Specialization.UNASSIGNED;
|
| 55 |
+
@Column(name = "department")
|
| 56 |
private Department department;
|
| 57 |
@CreationTimestamp
|
| 58 |
@Column(name = "created_at", nullable = false, updatable = false)
|
|
|
|
| 63 |
@Column(name = "must_change_password", nullable = false)
|
| 64 |
@Builder.Default
|
| 65 |
private boolean mustChangePassword = false;
|
| 66 |
+
}
|
src/main/java/com/attendenceSystem/module/user/entity/converter/DepartmentConverter.java
CHANGED
|
@@ -6,16 +6,23 @@ import jakarta.persistence.AttributeConverter;
|
|
| 6 |
import jakarta.persistence.Converter;
|
| 7 |
|
| 8 |
@Converter(autoApply = true)
|
| 9 |
-
public class DepartmentConverter implements AttributeConverter<Department,
|
| 10 |
|
| 11 |
@Override
|
| 12 |
-
public
|
| 13 |
-
return attribute == null ? null : attribute.
|
| 14 |
}
|
| 15 |
|
| 16 |
@Override
|
| 17 |
-
public Department convertToEntityAttribute(
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
}
|
| 20 |
-
|
| 21 |
}
|
|
|
|
| 6 |
import jakarta.persistence.Converter;
|
| 7 |
|
| 8 |
@Converter(autoApply = true)
|
| 9 |
+
public class DepartmentConverter implements AttributeConverter<Department, String> {
|
| 10 |
|
| 11 |
@Override
|
| 12 |
+
public String convertToDatabaseColumn(Department attribute) {
|
| 13 |
+
return attribute == null ? null : attribute.getRoomCode();
|
| 14 |
}
|
| 15 |
|
| 16 |
@Override
|
| 17 |
+
public Department convertToEntityAttribute(String dbData) {
|
| 18 |
+
if (dbData == null) {
|
| 19 |
+
return null;
|
| 20 |
+
}
|
| 21 |
+
for (Department dept : Department.values()) {
|
| 22 |
+
if (dept.getRoomCode().equals(dbData)) {
|
| 23 |
+
return dept;
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
return null;
|
| 27 |
}
|
|
|
|
| 28 |
}
|
src/main/java/com/attendenceSystem/module/user/entity/converter/SpecializationConverter.java
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.attendenceSystem.module.user.entity.converter;
|
| 2 |
+
|
| 3 |
+
import com.attendenceSystem.module.user.entity.enums.Specialization;
|
| 4 |
+
|
| 5 |
+
import jakarta.persistence.AttributeConverter;
|
| 6 |
+
import jakarta.persistence.Converter;
|
| 7 |
+
|
| 8 |
+
@Converter(autoApply = true)
|
| 9 |
+
public class SpecializationConverter implements AttributeConverter<Specialization, Integer> {
|
| 10 |
+
|
| 11 |
+
@Override
|
| 12 |
+
public Integer convertToDatabaseColumn(Specialization attribute) {
|
| 13 |
+
return attribute == null ? null : attribute.value;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
@Override
|
| 17 |
+
public Specialization convertToEntityAttribute(Integer dbData) {
|
| 18 |
+
return dbData == null ? null : Specialization.fromValue(dbData);
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
}
|
src/main/java/com/attendenceSystem/module/user/entity/enums/Department.java
CHANGED
|
@@ -6,17 +6,11 @@ import lombok.RequiredArgsConstructor;
|
|
| 6 |
@Getter
|
| 7 |
@RequiredArgsConstructor
|
| 8 |
public enum Department {
|
| 9 |
-
|
| 10 |
-
|
|
|
|
| 11 |
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
if (d.value == value) {
|
| 17 |
-
return d;
|
| 18 |
-
}
|
| 19 |
-
}
|
| 20 |
-
return null;
|
| 21 |
-
}
|
| 22 |
-
}
|
|
|
|
| 6 |
@Getter
|
| 7 |
@RequiredArgsConstructor
|
| 8 |
public enum Department {
|
| 9 |
+
DE213("DE213", "DE213-X7A9", "Phòng DE213"),
|
| 10 |
+
DE212("DE212", "DE212-X7A9", "Phòng DE212"),
|
| 11 |
+
DE211("DE211", "DE211-X7A9", "Phòng DE211");
|
| 12 |
|
| 13 |
+
private final String roomCode;
|
| 14 |
+
private final String joinCode;
|
| 15 |
+
private final String displayName;
|
| 16 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/main/java/com/attendenceSystem/module/user/entity/enums/Specialization.java
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package com.attendenceSystem.module.user.entity.enums;
|
| 2 |
+
|
| 3 |
+
import lombok.Getter;
|
| 4 |
+
import lombok.RequiredArgsConstructor;
|
| 5 |
+
|
| 6 |
+
@Getter
|
| 7 |
+
@RequiredArgsConstructor
|
| 8 |
+
public enum Specialization {
|
| 9 |
+
UNASSIGNED(0, "Chưa phân công"),
|
| 10 |
+
SOFTWARE_ENGINEERING(1, "Kỹ thuật phần mềm"),
|
| 11 |
+
INFORMATION_TECHNOLOGY(2, "Công nghệ thông tin"),
|
| 12 |
+
DATA_SCIENCE(3, "Khoa học dữ liệu");
|
| 13 |
+
|
| 14 |
+
public final int value;
|
| 15 |
+
private final String displayName;
|
| 16 |
+
|
| 17 |
+
public static Specialization fromValue(int value) {
|
| 18 |
+
for (Specialization s : Specialization.values()) {
|
| 19 |
+
if (s.value == value) {
|
| 20 |
+
return s;
|
| 21 |
+
}
|
| 22 |
+
}
|
| 23 |
+
return null;
|
| 24 |
+
}
|
| 25 |
+
}
|
src/main/java/com/attendenceSystem/module/user/mapper/request/RegisterRequestMapper.java
CHANGED
|
@@ -2,7 +2,7 @@ package com.attendenceSystem.module.user.mapper.request;
|
|
| 2 |
|
| 3 |
import com.attendenceSystem.module.user.dto.request.RegisterRequest;
|
| 4 |
import com.attendenceSystem.module.user.entity.User;
|
| 5 |
-
import com.attendenceSystem.module.user.entity.enums.
|
| 6 |
import com.attendenceSystem.module.user.entity.enums.Role;
|
| 7 |
import com.attendenceSystem.module.user.entity.enums.Status;
|
| 8 |
|
|
@@ -17,7 +17,7 @@ public final class RegisterRequestMapper {
|
|
| 17 |
.email(request.getEmail())
|
| 18 |
.password(hashPassword)
|
| 19 |
.fullName(request.getFullName())
|
| 20 |
-
.
|
| 21 |
.role(Role.EMPLOYEE)
|
| 22 |
.status(Status.PENDING)
|
| 23 |
.mustChangePassword(false)
|
|
|
|
| 2 |
|
| 3 |
import com.attendenceSystem.module.user.dto.request.RegisterRequest;
|
| 4 |
import com.attendenceSystem.module.user.entity.User;
|
| 5 |
+
import com.attendenceSystem.module.user.entity.enums.Specialization;
|
| 6 |
import com.attendenceSystem.module.user.entity.enums.Role;
|
| 7 |
import com.attendenceSystem.module.user.entity.enums.Status;
|
| 8 |
|
|
|
|
| 17 |
.email(request.getEmail())
|
| 18 |
.password(hashPassword)
|
| 19 |
.fullName(request.getFullName())
|
| 20 |
+
.specialization(Specialization.UNASSIGNED)
|
| 21 |
.role(Role.EMPLOYEE)
|
| 22 |
.status(Status.PENDING)
|
| 23 |
.mustChangePassword(false)
|
src/main/java/com/attendenceSystem/module/user/mapper/response/UserInformationResponseMapper.java
CHANGED
|
@@ -20,6 +20,7 @@ public class UserInformationResponseMapper {
|
|
| 20 |
.fullName(user.getFullName())
|
| 21 |
.role(user.getRole())
|
| 22 |
.status(user.getStatus())
|
|
|
|
| 23 |
.department(user.getDepartment())
|
| 24 |
.build();
|
| 25 |
}
|
|
|
|
| 20 |
.fullName(user.getFullName())
|
| 21 |
.role(user.getRole())
|
| 22 |
.status(user.getStatus())
|
| 23 |
+
.specialization(user.getSpecialization())
|
| 24 |
.department(user.getDepartment())
|
| 25 |
.build();
|
| 26 |
}
|
src/main/java/com/attendenceSystem/module/user/mapper/response/UserResponseMapper.java
CHANGED
|
@@ -20,9 +20,12 @@ public class UserResponseMapper {
|
|
| 20 |
.id(user.getId())
|
| 21 |
.username(user.getUsername())
|
| 22 |
.email(user.getEmail())
|
|
|
|
| 23 |
.fullName(user.getFullName())
|
| 24 |
.status(user.getStatus() == null ? null : user.getStatus().name())
|
| 25 |
.role(user.getRole().name())
|
|
|
|
|
|
|
| 26 |
.build();
|
| 27 |
}
|
| 28 |
}
|
|
|
|
| 20 |
.id(user.getId())
|
| 21 |
.username(user.getUsername())
|
| 22 |
.email(user.getEmail())
|
| 23 |
+
.phone(user.getPhone())
|
| 24 |
.fullName(user.getFullName())
|
| 25 |
.status(user.getStatus() == null ? null : user.getStatus().name())
|
| 26 |
.role(user.getRole().name())
|
| 27 |
+
.department(user.getDepartment() == null ? null : user.getDepartment().getRoomCode())
|
| 28 |
+
.departmentDisplay(user.getDepartment() == null ? "N/A" : user.getDepartment().getDisplayName())
|
| 29 |
.build();
|
| 30 |
}
|
| 31 |
}
|
src/main/java/com/attendenceSystem/module/user/repository/UserRepository.java
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
package com.attendenceSystem.module.user.repository;
|
| 2 |
|
|
|
|
| 3 |
import java.util.Optional;
|
| 4 |
|
| 5 |
import org.springframework.data.domain.Page;
|
|
@@ -8,6 +9,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
|
| 8 |
import org.springframework.stereotype.Repository;
|
| 9 |
|
| 10 |
import com.attendenceSystem.module.user.entity.User;
|
|
|
|
| 11 |
import com.attendenceSystem.module.user.entity.enums.Role;
|
| 12 |
import com.attendenceSystem.module.user.entity.enums.Status;
|
| 13 |
|
|
@@ -26,4 +28,6 @@ public interface UserRepository extends JpaRepository<User, Long> {
|
|
| 26 |
long countByRoleNot(Role role);
|
| 27 |
Optional<User> findByUsername(String username);
|
| 28 |
boolean existsByPhone(String phone);
|
|
|
|
|
|
|
| 29 |
}
|
|
|
|
| 1 |
package com.attendenceSystem.module.user.repository;
|
| 2 |
|
| 3 |
+
import java.util.List;
|
| 4 |
import java.util.Optional;
|
| 5 |
|
| 6 |
import org.springframework.data.domain.Page;
|
|
|
|
| 9 |
import org.springframework.stereotype.Repository;
|
| 10 |
|
| 11 |
import com.attendenceSystem.module.user.entity.User;
|
| 12 |
+
import com.attendenceSystem.module.user.entity.enums.Specialization;
|
| 13 |
import com.attendenceSystem.module.user.entity.enums.Role;
|
| 14 |
import com.attendenceSystem.module.user.entity.enums.Status;
|
| 15 |
|
|
|
|
| 28 |
long countByRoleNot(Role role);
|
| 29 |
Optional<User> findByUsername(String username);
|
| 30 |
boolean existsByPhone(String phone);
|
| 31 |
+
List<User> findBySpecializationAndRoleNot(Specialization specialization, Role role);
|
| 32 |
+
Page<User> findByRole(Role role, Pageable pageable);
|
| 33 |
}
|
src/main/java/com/attendenceSystem/module/user/service/AccountService.java
CHANGED
|
@@ -7,6 +7,9 @@ import com.attendenceSystem.module.user.dto.response.UserResponse;
|
|
| 7 |
|
| 8 |
public interface AccountService {
|
| 9 |
Page<UserResponse> getUsers(Pageable pageable);
|
|
|
|
| 10 |
UserResponse getUserById(Long id);
|
| 11 |
void deactivateUser(Long id);
|
| 12 |
-
void activateUser(Long id);
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
public interface AccountService {
|
| 9 |
Page<UserResponse> getUsers(Pageable pageable);
|
| 10 |
+
Page<UserResponse> getEmployees(Pageable pageable);
|
| 11 |
UserResponse getUserById(Long id);
|
| 12 |
void deactivateUser(Long id);
|
| 13 |
+
void activateUser(Long id);
|
| 14 |
+
void changeDepartment(Long id, String departmentCode);
|
| 15 |
+
}
|
src/main/java/com/attendenceSystem/module/user/service/impl/AccountServiceImpl.java
CHANGED
|
@@ -7,6 +7,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|
| 7 |
|
| 8 |
import com.attendenceSystem.module.user.dto.response.UserResponse;
|
| 9 |
import com.attendenceSystem.module.user.entity.User;
|
|
|
|
| 10 |
import com.attendenceSystem.module.user.entity.enums.Role;
|
| 11 |
import com.attendenceSystem.module.user.entity.enums.Status;
|
| 12 |
import com.attendenceSystem.module.user.mapper.response.UserResponseMapper;
|
|
@@ -27,6 +28,12 @@ public class AccountServiceImpl implements AccountService {
|
|
| 27 |
return userRepository.findAll(pageable).map(userResponseMapper::fromEntity);
|
| 28 |
}
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
@Override
|
| 31 |
public UserResponse getUserById(final Long id) {
|
| 32 |
User user = findById(id);
|
|
@@ -57,6 +64,38 @@ public class AccountServiceImpl implements AccountService {
|
|
| 57 |
userRepository.save(user);
|
| 58 |
}
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
private User findById(final Long id) {
|
| 61 |
return userRepository.findById(id)
|
| 62 |
.orElseThrow(() -> new IllegalArgumentException("Không tìm thấy người dùng với id: " + id));
|
|
|
|
| 7 |
|
| 8 |
import com.attendenceSystem.module.user.dto.response.UserResponse;
|
| 9 |
import com.attendenceSystem.module.user.entity.User;
|
| 10 |
+
import com.attendenceSystem.module.user.entity.enums.Department;
|
| 11 |
import com.attendenceSystem.module.user.entity.enums.Role;
|
| 12 |
import com.attendenceSystem.module.user.entity.enums.Status;
|
| 13 |
import com.attendenceSystem.module.user.mapper.response.UserResponseMapper;
|
|
|
|
| 28 |
return userRepository.findAll(pageable).map(userResponseMapper::fromEntity);
|
| 29 |
}
|
| 30 |
|
| 31 |
+
@Override
|
| 32 |
+
public Page<UserResponse> getEmployees(final Pageable pageable) {
|
| 33 |
+
return userRepository.findByRole(Role.EMPLOYEE, pageable)
|
| 34 |
+
.map(userResponseMapper::fromEntity);
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
@Override
|
| 38 |
public UserResponse getUserById(final Long id) {
|
| 39 |
User user = findById(id);
|
|
|
|
| 64 |
userRepository.save(user);
|
| 65 |
}
|
| 66 |
|
| 67 |
+
@Transactional
|
| 68 |
+
@Override
|
| 69 |
+
public void changeDepartment(final Long id, final String departmentCode) {
|
| 70 |
+
User user = findById(id);
|
| 71 |
+
|
| 72 |
+
// Chỉ manager mới có quyền chuyển phòng
|
| 73 |
+
if (!SecurityUtil.isAuthenticated()) {
|
| 74 |
+
throw new IllegalStateException("Không xác định được tài khoản hiện tại");
|
| 75 |
+
}
|
| 76 |
+
Role currentUserRole = SecurityUtil.getCurrentUserRole();
|
| 77 |
+
if (currentUserRole != Role.MANAGER && currentUserRole != Role.ADMIN) {
|
| 78 |
+
throw new IllegalStateException("Bạn không có quyền thực hiện hành động này");
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
// Chỉ thao tác trên employee
|
| 82 |
+
if (user.getRole() != Role.EMPLOYEE) {
|
| 83 |
+
throw new IllegalStateException("Chỉ có thể chuyển phòng cho nhân viên");
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
Department department = null;
|
| 87 |
+
if (departmentCode != null && !departmentCode.isBlank()) {
|
| 88 |
+
try {
|
| 89 |
+
department = Department.valueOf(departmentCode);
|
| 90 |
+
} catch (IllegalArgumentException e) {
|
| 91 |
+
throw new IllegalArgumentException("Mã phòng không hợp lệ: " + departmentCode);
|
| 92 |
+
}
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
user.setDepartment(department);
|
| 96 |
+
userRepository.save(user);
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
private User findById(final Long id) {
|
| 100 |
return userRepository.findById(id)
|
| 101 |
.orElseThrow(() -> new IllegalArgumentException("Không tìm thấy người dùng với id: " + id));
|
src/main/java/com/attendenceSystem/module/user/service/impl/UserServiceImpl.java
CHANGED
|
@@ -79,8 +79,8 @@ public class UserServiceImpl implements UserService {
|
|
| 79 |
}
|
| 80 |
currentUser.setEmail(request.getEmail());
|
| 81 |
}
|
| 82 |
-
if (request.
|
| 83 |
-
currentUser.
|
| 84 |
}
|
| 85 |
userRepository.save(currentUser);
|
| 86 |
return userInformationResponseMapper.fromEntity(currentUser);
|
|
|
|
| 79 |
}
|
| 80 |
currentUser.setEmail(request.getEmail());
|
| 81 |
}
|
| 82 |
+
if (request.getSpecialization() != null) {
|
| 83 |
+
currentUser.setSpecialization(request.getSpecialization());
|
| 84 |
}
|
| 85 |
userRepository.save(currentUser);
|
| 86 |
return userInformationResponseMapper.fromEntity(currentUser);
|
src/main/java/com/attendenceSystem/security/CustomUserDetails.java
CHANGED
|
@@ -35,7 +35,7 @@ public class CustomUserDetails implements UserDetails {
|
|
| 35 |
user.getFullName(),
|
| 36 |
user.getEmail(),
|
| 37 |
user.getPhone(),
|
| 38 |
-
user.
|
| 39 |
user.getRole().name(),
|
| 40 |
user.getStatus());
|
| 41 |
}
|
|
@@ -48,7 +48,7 @@ public class CustomUserDetails implements UserDetails {
|
|
| 48 |
response.fullName(),
|
| 49 |
response.email(),
|
| 50 |
response.phone(),
|
| 51 |
-
response.
|
| 52 |
response.role().name(),
|
| 53 |
response.status());
|
| 54 |
}
|
|
|
|
| 35 |
user.getFullName(),
|
| 36 |
user.getEmail(),
|
| 37 |
user.getPhone(),
|
| 38 |
+
user.getSpecialization() == null ? null : user.getSpecialization().name(),
|
| 39 |
user.getRole().name(),
|
| 40 |
user.getStatus());
|
| 41 |
}
|
|
|
|
| 48 |
response.fullName(),
|
| 49 |
response.email(),
|
| 50 |
response.phone(),
|
| 51 |
+
response.specialization() == null ? null : response.specialization().name(),
|
| 52 |
response.role().name(),
|
| 53 |
response.status());
|
| 54 |
}
|
src/main/resources/static/css/account/employee-detail.css
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import url("../base.css");
|
| 2 |
+
|
| 3 |
+
/* ===================================
|
| 4 |
+
HEADER
|
| 5 |
+
=================================== */
|
| 6 |
+
|
| 7 |
+
.container-header {
|
| 8 |
+
margin-bottom: 24px;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
.container-header h1 {
|
| 12 |
+
font-size: 1.8rem;
|
| 13 |
+
font-weight: 700;
|
| 14 |
+
color: var(--text-white);
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
.container-header p {
|
| 18 |
+
color: var(--text-light);
|
| 19 |
+
font-size: 0.9rem;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
/* ===================================
|
| 23 |
+
BACK BUTTON
|
| 24 |
+
=================================== */
|
| 25 |
+
|
| 26 |
+
.back-btn {
|
| 27 |
+
display: inline-flex;
|
| 28 |
+
align-items: center;
|
| 29 |
+
gap: 8px;
|
| 30 |
+
padding: 10px 20px;
|
| 31 |
+
border-radius: 12px;
|
| 32 |
+
background: rgba(255, 255, 255, 0.06);
|
| 33 |
+
color: var(--text-light);
|
| 34 |
+
font-size: 0.85rem;
|
| 35 |
+
font-weight: 600;
|
| 36 |
+
text-decoration: none;
|
| 37 |
+
transition: 0.25s;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.back-btn:hover {
|
| 41 |
+
background: rgba(255, 255, 255, 0.12);
|
| 42 |
+
color: var(--text-white);
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
/* ===================================
|
| 46 |
+
PROFILE CARD
|
| 47 |
+
=================================== */
|
| 48 |
+
|
| 49 |
+
.container-card {
|
| 50 |
+
background: var(--bg-card);
|
| 51 |
+
backdrop-filter: blur(20px);
|
| 52 |
+
border: 1px solid var(--border);
|
| 53 |
+
border-radius: 24px;
|
| 54 |
+
overflow: hidden;
|
| 55 |
+
margin-bottom: 24px;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
.card-header {
|
| 59 |
+
padding: 24px;
|
| 60 |
+
display: flex;
|
| 61 |
+
justify-content: space-between;
|
| 62 |
+
align-items: center;
|
| 63 |
+
border-bottom: 1px solid var(--border);
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
.card-header h2 {
|
| 67 |
+
font-size: 1.3rem;
|
| 68 |
+
font-weight: 700;
|
| 69 |
+
color: var(--text-white);
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
.card-body {
|
| 73 |
+
padding: 28px;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
/* ===================================
|
| 77 |
+
INFO GRID
|
| 78 |
+
=================================== */
|
| 79 |
+
|
| 80 |
+
.info-grid {
|
| 81 |
+
display: grid;
|
| 82 |
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
| 83 |
+
gap: 24px;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
.info-item {
|
| 87 |
+
display: flex;
|
| 88 |
+
flex-direction: column;
|
| 89 |
+
gap: 8px;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
.info-item label {
|
| 93 |
+
color: var(--text-light);
|
| 94 |
+
font-size: 0.85rem;
|
| 95 |
+
font-weight: 600;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.info-item p {
|
| 99 |
+
color: var(--text-white);
|
| 100 |
+
font-size: 1rem;
|
| 101 |
+
font-weight: 500;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
/* ===================================
|
| 105 |
+
STATUS
|
| 106 |
+
=================================== */
|
| 107 |
+
|
| 108 |
+
.status {
|
| 109 |
+
display: inline-flex;
|
| 110 |
+
align-items: center;
|
| 111 |
+
padding: 6px 14px;
|
| 112 |
+
border-radius: 999px;
|
| 113 |
+
font-size: 0.85rem;
|
| 114 |
+
font-weight: 600;
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
.active {
|
| 118 |
+
background: rgba(34, 197, 94, 0.15);
|
| 119 |
+
color: var(--success);
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.inactive {
|
| 123 |
+
background: rgba(245, 158, 11, 0.15);
|
| 124 |
+
color: var(--warning);
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
.locked {
|
| 128 |
+
background: rgba(239, 68, 68, 0.15);
|
| 129 |
+
color: var(--danger);
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
/* ===================================
|
| 133 |
+
CHANGE DEPARTMENT CARD
|
| 134 |
+
=================================== */
|
| 135 |
+
|
| 136 |
+
.dashboard-card {
|
| 137 |
+
background: var(--bg-card);
|
| 138 |
+
backdrop-filter: blur(20px);
|
| 139 |
+
border: 1px solid var(--border);
|
| 140 |
+
border-radius: 24px;
|
| 141 |
+
overflow: hidden;
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
.change-dept-form .form-group {
|
| 145 |
+
padding: 24px;
|
| 146 |
+
display: flex;
|
| 147 |
+
flex-direction: column;
|
| 148 |
+
gap: 12px;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
.change-dept-form label {
|
| 152 |
+
color: var(--text-light);
|
| 153 |
+
font-size: 0.9rem;
|
| 154 |
+
font-weight: 600;
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
.change-dept-form select {
|
| 158 |
+
width: 100%;
|
| 159 |
+
height: 52px;
|
| 160 |
+
padding: 0 18px;
|
| 161 |
+
border-radius: 14px;
|
| 162 |
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
| 163 |
+
background: rgba(255, 255, 255, 0.05);
|
| 164 |
+
color: var(--text-white);
|
| 165 |
+
font-size: 0.95rem;
|
| 166 |
+
cursor: pointer;
|
| 167 |
+
transition: 0.25s;
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
.change-dept-form select:focus {
|
| 171 |
+
outline: none;
|
| 172 |
+
border-color: var(--indigo-06);
|
| 173 |
+
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.15);
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
.change-dept-form option {
|
| 177 |
+
background: #0f172a;
|
| 178 |
+
color: white;
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
/* ===================================
|
| 182 |
+
RESPONSIVE
|
| 183 |
+
=================================== */
|
| 184 |
+
|
| 185 |
+
@media (max-width: 768px) {
|
| 186 |
+
.info-grid {
|
| 187 |
+
grid-template-columns: 1fr;
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
.card-header {
|
| 191 |
+
flex-direction: column;
|
| 192 |
+
align-items: flex-start;
|
| 193 |
+
gap: 15px;
|
| 194 |
+
}
|
| 195 |
+
}
|
src/main/resources/static/css/document/document-create.css
CHANGED
|
@@ -188,4 +188,72 @@ input[type="file"]{
|
|
| 188 |
width:100%;
|
| 189 |
justify-content:center;
|
| 190 |
}
|
| 191 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
width:100%;
|
| 189 |
justify-content:center;
|
| 190 |
}
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
/* ===================================
|
| 194 |
+
CHECKBOX SYSTEM
|
| 195 |
+
=================================== */
|
| 196 |
+
|
| 197 |
+
.checkbox-container{
|
| 198 |
+
display:flex;
|
| 199 |
+
|
| 200 |
+
flex-direction:column;
|
| 201 |
+
|
| 202 |
+
gap:10px;
|
| 203 |
+
|
| 204 |
+
padding:12px 14px;
|
| 205 |
+
|
| 206 |
+
border-radius:14px;
|
| 207 |
+
|
| 208 |
+
border:1px solid var(--border);
|
| 209 |
+
|
| 210 |
+
background:var(--bg-table-06);
|
| 211 |
+
|
| 212 |
+
min-height:120px;
|
| 213 |
+
|
| 214 |
+
max-height:300px;
|
| 215 |
+
|
| 216 |
+
overflow-y:auto;
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
.checkbox-label{
|
| 220 |
+
display:flex;
|
| 221 |
+
|
| 222 |
+
align-items:center;
|
| 223 |
+
|
| 224 |
+
gap:10px;
|
| 225 |
+
|
| 226 |
+
padding:8px 10px;
|
| 227 |
+
|
| 228 |
+
border-radius:10px;
|
| 229 |
+
|
| 230 |
+
cursor:pointer;
|
| 231 |
+
|
| 232 |
+
transition:.25s;
|
| 233 |
+
|
| 234 |
+
color:var(--text-white);
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
.checkbox-label:hover{
|
| 238 |
+
background:var(--bg-table);
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
.checkbox-label input[type="checkbox"]{
|
| 242 |
+
width:18px;
|
| 243 |
+
|
| 244 |
+
height:18px;
|
| 245 |
+
|
| 246 |
+
cursor:pointer;
|
| 247 |
+
|
| 248 |
+
accent-color:var(--indigo-06);
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
.placeholder-text{
|
| 252 |
+
color:var(--text-gray);
|
| 253 |
+
|
| 254 |
+
font-style:italic;
|
| 255 |
+
|
| 256 |
+
padding:20px;
|
| 257 |
+
|
| 258 |
+
text-align:center;
|
| 259 |
+
}
|
src/main/resources/templates/cms/account/employee-detail.html
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
|
| 3 |
+
<html xmlns:th="http://www.thymeleaf.org">
|
| 4 |
+
|
| 5 |
+
<head>
|
| 6 |
+
<meta charset="UTF-8">
|
| 7 |
+
<title>Chi tiết nhân viên</title>
|
| 8 |
+
|
| 9 |
+
<link rel="stylesheet" th:href="@{/css/account/employee-detail.css}">
|
| 10 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
| 11 |
+
</head>
|
| 12 |
+
|
| 13 |
+
<body>
|
| 14 |
+
|
| 15 |
+
<div class="bg-image"></div>
|
| 16 |
+
|
| 17 |
+
<div class="layout">
|
| 18 |
+
|
| 19 |
+
<div th:replace="~{cms/sidebar/sidebar-role :: sidebar}"></div>
|
| 20 |
+
|
| 21 |
+
<div class="container-layout">
|
| 22 |
+
|
| 23 |
+
<!-- HEADER -->
|
| 24 |
+
<div class="container-header">
|
| 25 |
+
<h1>Chi tiết nhân viên</h1>
|
| 26 |
+
<p>Thông tin chi tiết của nhân viên</p>
|
| 27 |
+
</div>
|
| 28 |
+
|
| 29 |
+
<main class="main-container">
|
| 30 |
+
|
| 31 |
+
<section class="container-card">
|
| 32 |
+
|
| 33 |
+
<!-- PROFILE CARD -->
|
| 34 |
+
<div class="card-header">
|
| 35 |
+
<h2>Thông tin cá nhân</h2>
|
| 36 |
+
<a th:href="@{/manager/accounts}" class="back-btn">
|
| 37 |
+
<i class="fas fa-arrow-left"></i>
|
| 38 |
+
Quay lại
|
| 39 |
+
</a>
|
| 40 |
+
</div>
|
| 41 |
+
|
| 42 |
+
<div class="card-body">
|
| 43 |
+
<div class="info-grid">
|
| 44 |
+
|
| 45 |
+
<div class="info-item">
|
| 46 |
+
<label>Họ và tên</label>
|
| 47 |
+
<p th:text="${user.fullName()}">Nguyễn Văn A</p>
|
| 48 |
+
</div>
|
| 49 |
+
|
| 50 |
+
<div class="info-item">
|
| 51 |
+
<label>Email</label>
|
| 52 |
+
<p th:text="${user.email()}">nguyenvana@company.com</p>
|
| 53 |
+
</div>
|
| 54 |
+
|
| 55 |
+
<div class="info-item">
|
| 56 |
+
<label>Số điện thoại</label>
|
| 57 |
+
<p th:text="${user.phone() != null && !user.phone().isEmpty() ? user.phone() : 'Chưa có'}">Chưa có</p>
|
| 58 |
+
</div>
|
| 59 |
+
|
| 60 |
+
<div class="info-item">
|
| 61 |
+
<label>Mã nhân viên</label>
|
| 62 |
+
<p th:text="${user.username()}">EMP001</p>
|
| 63 |
+
</div>
|
| 64 |
+
|
| 65 |
+
<div class="info-item">
|
| 66 |
+
<label>Phòng ban</label>
|
| 67 |
+
<p th:text="${user.departmentDisplay()}">N/A</p>
|
| 68 |
+
</div>
|
| 69 |
+
|
| 70 |
+
<div class="info-item">
|
| 71 |
+
<label>Vai trò</label>
|
| 72 |
+
<p th:text="${user.role() == 'EMPLOYEE' ? 'Nhân viên' : (user.role() == 'MANAGER' ? 'Quản lý' : (user.role() == 'ADMIN' ? 'Quản trị viên' : user.role()))}">Nhân viên</p>
|
| 73 |
+
</div>
|
| 74 |
+
|
| 75 |
+
<div class="info-item">
|
| 76 |
+
<label>Trạng thái</label>
|
| 77 |
+
<span th:text="${user.status() == 'ACTIVE' ? 'Đang làm việc' : (user.status() == 'INACTIVE' ? 'Tạm khóa' : (user.status() == 'PENDING' ? 'Chờ duyệt' : 'Không xác định'))}"
|
| 78 |
+
th:class="${'status ' + (user.status() == 'ACTIVE' ? 'active' : (user.status() == 'INACTIVE' ? 'inactive' : 'locked'))}">
|
| 79 |
+
Đang làm việc
|
| 80 |
+
</span>
|
| 81 |
+
</div>
|
| 82 |
+
|
| 83 |
+
</div>
|
| 84 |
+
</div>
|
| 85 |
+
|
| 86 |
+
</section>
|
| 87 |
+
|
| 88 |
+
<!-- CHANGE DEPARTMENT CARD -->
|
| 89 |
+
<section class="dashboard-card">
|
| 90 |
+
|
| 91 |
+
<div class="card-header">
|
| 92 |
+
<h2>Chuyển phòng</h2>
|
| 93 |
+
</div>
|
| 94 |
+
|
| 95 |
+
<div class="card-body">
|
| 96 |
+
|
| 97 |
+
<form th:action="@{/manager/accounts/update/{id}(id=${user.id()})}" method="post" class="change-dept-form">
|
| 98 |
+
|
| 99 |
+
<div class="form-group">
|
| 100 |
+
<label for="department">Chọn phòng mới</label>
|
| 101 |
+
<select id="department" name="department" onchange="this.form.submit()">
|
| 102 |
+
<option value="">-- Chọn phòng ban --</option>
|
| 103 |
+
<option th:each="dept : ${departments}"
|
| 104 |
+
th:value="${dept.name()}"
|
| 105 |
+
th:text="${dept.displayName}"
|
| 106 |
+
th:selected="${user.department() == dept.roomCode}">
|
| 107 |
+
</option>
|
| 108 |
+
</select>
|
| 109 |
+
</div>
|
| 110 |
+
|
| 111 |
+
</form>
|
| 112 |
+
|
| 113 |
+
</div>
|
| 114 |
+
|
| 115 |
+
</section>
|
| 116 |
+
|
| 117 |
+
</main>
|
| 118 |
+
|
| 119 |
+
<div th:replace="~{cms/layout/footer :: footer}"></div>
|
| 120 |
+
|
| 121 |
+
</div>
|
| 122 |
+
|
| 123 |
+
</div>
|
| 124 |
+
|
| 125 |
+
</body>
|
| 126 |
+
|
| 127 |
+
</html>
|
src/main/resources/templates/cms/account/employee-list.html
CHANGED
|
@@ -51,10 +51,7 @@
|
|
| 51 |
|
| 52 |
<select>
|
| 53 |
<option>Tất cả phòng ban</option>
|
| 54 |
-
<option
|
| 55 |
-
<option>Phòng Nhân sự</option>
|
| 56 |
-
<option>Phòng Kế toán</option>
|
| 57 |
-
<option>Phòng Marketing</option>
|
| 58 |
</select>
|
| 59 |
|
| 60 |
<select>
|
|
@@ -95,25 +92,25 @@
|
|
| 95 |
|
| 96 |
<tbody>
|
| 97 |
|
| 98 |
-
<tr>
|
| 99 |
|
| 100 |
<td>
|
| 101 |
|
| 102 |
<div class="user">
|
| 103 |
|
| 104 |
-
<div class="avatar">H</div>
|
| 105 |
|
| 106 |
-
<strong>Nguyễn Minh Hải</strong>
|
| 107 |
|
| 108 |
</div>
|
| 109 |
|
| 110 |
</td>
|
| 111 |
|
| 112 |
-
<td>EMP001</td>
|
| 113 |
|
| 114 |
-
<td>hai@gmail.com</td>
|
| 115 |
|
| 116 |
-
<td>IT</td>
|
| 117 |
|
| 118 |
<td>Nhân viên</td>
|
| 119 |
|
|
@@ -129,69 +126,16 @@
|
|
| 129 |
|
| 130 |
<div class="actions">
|
| 131 |
|
| 132 |
-
<
|
| 133 |
Chi tiết
|
| 134 |
-
</
|
| 135 |
-
|
| 136 |
-
<
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
</button>
|
| 143 |
-
|
| 144 |
-
</div>
|
| 145 |
-
|
| 146 |
-
</td>
|
| 147 |
-
|
| 148 |
-
</tr>
|
| 149 |
-
|
| 150 |
-
<tr>
|
| 151 |
-
|
| 152 |
-
<td>
|
| 153 |
-
|
| 154 |
-
<div class="user">
|
| 155 |
-
|
| 156 |
-
<div class="avatar">A</div>
|
| 157 |
-
|
| 158 |
-
<strong>Nguyễn Văn An</strong>
|
| 159 |
-
|
| 160 |
-
</div>
|
| 161 |
-
|
| 162 |
-
</td>
|
| 163 |
-
|
| 164 |
-
<td>EMP002</td>
|
| 165 |
-
|
| 166 |
-
<td>an@gmail.com</td>
|
| 167 |
-
|
| 168 |
-
<td>Nhân sự</td>
|
| 169 |
-
|
| 170 |
-
<td>Trưởng phòng</td>
|
| 171 |
-
|
| 172 |
-
<td>
|
| 173 |
-
|
| 174 |
-
<span class="status inactive">
|
| 175 |
-
Nghỉ việc
|
| 176 |
-
</span>
|
| 177 |
-
|
| 178 |
-
</td>
|
| 179 |
-
|
| 180 |
-
<td>
|
| 181 |
-
|
| 182 |
-
<div class="actions">
|
| 183 |
-
|
| 184 |
-
<button class="icon-btn">
|
| 185 |
-
Chi tiết
|
| 186 |
-
</button>
|
| 187 |
-
|
| 188 |
-
<button class="icon-btn btn-role">
|
| 189 |
-
Chuyển phòng
|
| 190 |
-
</button>
|
| 191 |
-
|
| 192 |
-
<button class="icon-btn lock">
|
| 193 |
-
Mở khóa
|
| 194 |
-
</button>
|
| 195 |
|
| 196 |
</div>
|
| 197 |
|
|
|
|
| 51 |
|
| 52 |
<select>
|
| 53 |
<option>Tất cả phòng ban</option>
|
| 54 |
+
<option th:each="dept : ${departments}" th:value="${dept.name()}" th:text="${dept.displayName}"></option>
|
|
|
|
|
|
|
|
|
|
| 55 |
</select>
|
| 56 |
|
| 57 |
<select>
|
|
|
|
| 92 |
|
| 93 |
<tbody>
|
| 94 |
|
| 95 |
+
<tr th:each="user : ${users.content}">
|
| 96 |
|
| 97 |
<td>
|
| 98 |
|
| 99 |
<div class="user">
|
| 100 |
|
| 101 |
+
<div class="avatar" th:text="${#strings.substring(user.fullName(), 0, 1)}">H</div>
|
| 102 |
|
| 103 |
+
<strong th:text="${user.fullName()}">Nguyễn Minh Hải</strong>
|
| 104 |
|
| 105 |
</div>
|
| 106 |
|
| 107 |
</td>
|
| 108 |
|
| 109 |
+
<td th:text="${user.username()}">EMP001</td>
|
| 110 |
|
| 111 |
+
<td th:text="${user.email()}">hai@gmail.com</td>
|
| 112 |
|
| 113 |
+
<td th:text="${user.departmentDisplay()}">IT</td>
|
| 114 |
|
| 115 |
<td>Nhân viên</td>
|
| 116 |
|
|
|
|
| 126 |
|
| 127 |
<div class="actions">
|
| 128 |
|
| 129 |
+
<a th:href="@{/manager/accounts/{id}(id=${user.id()})}" class="icon-btn">
|
| 130 |
Chi tiết
|
| 131 |
+
</a>
|
| 132 |
+
|
| 133 |
+
<form th:action="@{/manager/accounts/update/{id}(id=${user.id()})}" method="post" style="display:inline;">
|
| 134 |
+
<select name="department" onchange="this.form.submit()">
|
| 135 |
+
<option value="">Chuyển phòng</option>
|
| 136 |
+
<option th:each="dept : ${departments}" th:value="${dept.name()}" th:text="${dept.displayName}"></option>
|
| 137 |
+
</select>
|
| 138 |
+
</form>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
</div>
|
| 141 |
|
src/main/resources/templates/cms/dashboard/dashboard-employee.html
CHANGED
|
@@ -115,7 +115,7 @@
|
|
| 115 |
</div>
|
| 116 |
</a>
|
| 117 |
|
| 118 |
-
<a href="/
|
| 119 |
<div class="quick-action-card__icon">
|
| 120 |
<i class="bi bi-file-earmark-arrow-up-fill"></i>
|
| 121 |
</div>
|
|
|
|
| 115 |
</div>
|
| 116 |
</a>
|
| 117 |
|
| 118 |
+
<a href="/report/create" class="quick-action-card">
|
| 119 |
<div class="quick-action-card__icon">
|
| 120 |
<i class="bi bi-file-earmark-arrow-up-fill"></i>
|
| 121 |
</div>
|
src/main/resources/templates/cms/document/document-create.html
CHANGED
|
@@ -41,6 +41,12 @@
|
|
| 41 |
|
| 42 |
<form method="post" th:action="@{/report/create}" enctype="multipart/form-data">
|
| 43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
<!-- Nội dung -->
|
| 45 |
<div class="form-group">
|
| 46 |
<label>Nội dung báo cáo</label>
|
|
@@ -55,19 +61,19 @@
|
|
| 55 |
<select name="departmentId" id="departmentSelect" required>
|
| 56 |
<option value="" disabled selected>Chọn phòng ban</option>
|
| 57 |
|
| 58 |
-
<option th:each="d : ${departments}" th:value="${d.
|
| 59 |
</option>
|
| 60 |
</select>
|
| 61 |
</div>
|
| 62 |
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
|
| 72 |
<!-- Upload file -->
|
| 73 |
<div class="form-group">
|
|
@@ -117,18 +123,33 @@
|
|
| 117 |
|
| 118 |
const departmentId = this.value;
|
| 119 |
|
| 120 |
-
fetch(`/api/users/by-department/${departmentId}`)
|
| 121 |
.then(res => res.json())
|
| 122 |
.then(data => {
|
| 123 |
|
| 124 |
-
const
|
| 125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
data.forEach(user => {
|
| 128 |
-
const
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
});
|
| 133 |
|
| 134 |
});
|
|
@@ -138,4 +159,4 @@
|
|
| 138 |
|
| 139 |
</body>
|
| 140 |
|
| 141 |
-
</html>
|
|
|
|
| 41 |
|
| 42 |
<form method="post" th:action="@{/report/create}" enctype="multipart/form-data">
|
| 43 |
|
| 44 |
+
<!-- Tiêu đề -->
|
| 45 |
+
<div class="form-group">
|
| 46 |
+
<label>Tiêu đề báo cáo</label>
|
| 47 |
+
<input type="text" name="title" placeholder="Nhập tiêu đề báo cáo..." required>
|
| 48 |
+
</div>
|
| 49 |
+
|
| 50 |
<!-- Nội dung -->
|
| 51 |
<div class="form-group">
|
| 52 |
<label>Nội dung báo cáo</label>
|
|
|
|
| 61 |
<select name="departmentId" id="departmentSelect" required>
|
| 62 |
<option value="" disabled selected>Chọn phòng ban</option>
|
| 63 |
|
| 64 |
+
<option th:each="d : ${departments}" th:value="${d.value}" th:text="${d.displayName}">
|
| 65 |
</option>
|
| 66 |
</select>
|
| 67 |
</div>
|
| 68 |
|
| 69 |
+
<!-- Người được xem -->
|
| 70 |
+
<div class="form-group">
|
| 71 |
+
<label>Người được chia sẻ</label>
|
| 72 |
|
| 73 |
+
<div id="userCheckboxes" class="checkbox-container">
|
| 74 |
+
<p class="placeholder-text">Chọn phòng ban trước</p>
|
| 75 |
+
</div>
|
| 76 |
+
</div>
|
| 77 |
|
| 78 |
<!-- Upload file -->
|
| 79 |
<div class="form-group">
|
|
|
|
| 123 |
|
| 124 |
const departmentId = this.value;
|
| 125 |
|
| 126 |
+
fetch(`/api/report/users/by-department/${departmentId}`)
|
| 127 |
.then(res => res.json())
|
| 128 |
.then(data => {
|
| 129 |
|
| 130 |
+
const container = document.getElementById("userCheckboxes");
|
| 131 |
+
container.innerHTML = "";
|
| 132 |
+
|
| 133 |
+
if (data.length === 0) {
|
| 134 |
+
container.innerHTML = "<p class='placeholder-text'>Không có người dùng trong phòng ban này</p>";
|
| 135 |
+
return;
|
| 136 |
+
}
|
| 137 |
|
| 138 |
data.forEach(user => {
|
| 139 |
+
const label = document.createElement("label");
|
| 140 |
+
label.className = "checkbox-label";
|
| 141 |
+
|
| 142 |
+
const checkbox = document.createElement("input");
|
| 143 |
+
checkbox.type = "checkbox";
|
| 144 |
+
checkbox.name = "sharedUserIds";
|
| 145 |
+
checkbox.value = user.id;
|
| 146 |
+
|
| 147 |
+
const span = document.createElement("span");
|
| 148 |
+
span.textContent = user.fullName;
|
| 149 |
+
|
| 150 |
+
label.appendChild(checkbox);
|
| 151 |
+
label.appendChild(span);
|
| 152 |
+
container.appendChild(label);
|
| 153 |
});
|
| 154 |
|
| 155 |
});
|
|
|
|
| 159 |
|
| 160 |
</body>
|
| 161 |
|
| 162 |
+
</html>
|
src/main/resources/templates/cms/document/document-list.html
CHANGED
|
@@ -40,85 +40,14 @@
|
|
| 40 |
</tr>
|
| 41 |
</thead>
|
| 42 |
|
| 43 |
-
<tbody>
|
| 44 |
-
|
| 45 |
<tr>
|
| 46 |
-
|
| 47 |
-
<td>
|
| 48 |
-
Nguyễn Văn A
|
| 49 |
-
</td>
|
| 50 |
-
|
| 51 |
-
<td>
|
| 52 |
-
Báo cáo tuần 01
|
| 53 |
-
</td>
|
| 54 |
-
|
| 55 |
-
<td>
|
| 56 |
-
15/02/2025
|
| 57 |
-
</td>
|
| 58 |
-
|
| 59 |
-
<td>
|
| 60 |
-
<a class=" file-link">
|
| 61 |
-
📎 Tải file
|
| 62 |
-
</a>
|
| 63 |
-
|
| 64 |
-
<span>
|
| 65 |
-
Không có
|
| 66 |
-
</span>
|
| 67 |
-
</td>
|
| 68 |
-
|
| 69 |
-
<td>
|
| 70 |
-
<a target="_blank" class="file-link">
|
| 71 |
-
🔗 Mở link
|
| 72 |
-
</a>
|
| 73 |
-
|
| 74 |
-
<span>
|
| 75 |
-
Không có
|
| 76 |
-
</span>
|
| 77 |
-
</td>
|
| 78 |
-
|
| 79 |
-
<td>
|
| 80 |
-
|
| 81 |
-
<span class="status-pending">
|
| 82 |
-
Chờ duyệt
|
| 83 |
-
</span>
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
</td>
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
<td>
|
| 92 |
-
|
| 93 |
-
<div class="action-group" >
|
| 94 |
-
|
| 95 |
-
<form method="post">
|
| 96 |
-
|
| 97 |
-
<button class="btn btn-success">
|
| 98 |
-
Duyệt
|
| 99 |
-
</button>
|
| 100 |
-
|
| 101 |
-
</form>
|
| 102 |
-
|
| 103 |
-
<form method="post">
|
| 104 |
-
|
| 105 |
-
<button class="btn btn-danger">
|
| 106 |
-
Từ chối
|
| 107 |
-
</button>
|
| 108 |
-
|
| 109 |
-
</form>
|
| 110 |
-
|
| 111 |
-
</div>
|
| 112 |
-
|
| 113 |
-
</td>
|
| 114 |
-
|
| 115 |
</tr>
|
| 116 |
-
|
| 117 |
</tbody>
|
| 118 |
</table>
|
| 119 |
</div>
|
| 120 |
|
| 121 |
-
|
| 122 |
<div class="pagination">
|
| 123 |
<a class="btn btn-pagination">
|
| 124 |
← Trước
|
|
@@ -140,6 +69,76 @@
|
|
| 140 |
|
| 141 |
</div>
|
| 142 |
</div>
|
| 143 |
-
</body>
|
| 144 |
|
| 145 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
</tr>
|
| 41 |
</thead>
|
| 42 |
|
| 43 |
+
<tbody id="reportTableBody">
|
|
|
|
| 44 |
<tr>
|
| 45 |
+
<td colspan="7" style="text-align:center">Đang tải...</td>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
</tr>
|
|
|
|
| 47 |
</tbody>
|
| 48 |
</table>
|
| 49 |
</div>
|
| 50 |
|
|
|
|
| 51 |
<div class="pagination">
|
| 52 |
<a class="btn btn-pagination">
|
| 53 |
← Trước
|
|
|
|
| 69 |
|
| 70 |
</div>
|
| 71 |
</div>
|
|
|
|
| 72 |
|
| 73 |
+
<script>
|
| 74 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 75 |
+
const tableBody = document.getElementById('reportTableBody');
|
| 76 |
+
|
| 77 |
+
Promise.all([
|
| 78 |
+
fetch('/api/report/my-reports').then(res => res.json()),
|
| 79 |
+
fetch('/api/report/shared-with-me').then(res => res.json())
|
| 80 |
+
])
|
| 81 |
+
.then(([myReports, sharedReports]) => {
|
| 82 |
+
tableBody.innerHTML = '';
|
| 83 |
+
|
| 84 |
+
const allReports = [];
|
| 85 |
+
|
| 86 |
+
if (myReports.content && myReports.content.length > 0) {
|
| 87 |
+
allReports.push(...myReports.content);
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
if (sharedReports.content && sharedReports.content.length > 0) {
|
| 91 |
+
// Add reports shared with me that are not already in my reports
|
| 92 |
+
sharedReports.content.forEach(shared => {
|
| 93 |
+
if (!allReports.some(r => r.id === shared.id)) {
|
| 94 |
+
allReports.push(shared);
|
| 95 |
+
}
|
| 96 |
+
});
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
// Sort by createdAt descending
|
| 100 |
+
allReports.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
| 101 |
+
|
| 102 |
+
if (allReports.length > 0) {
|
| 103 |
+
allReports.forEach(report => {
|
| 104 |
+
const row = document.createElement('tr');
|
| 105 |
+
|
| 106 |
+
const date = new Date(report.createdAt);
|
| 107 |
+
const formattedDate = date.toLocaleDateString('vi-VN');
|
| 108 |
+
|
| 109 |
+
const employeeName = report.employeeName || 'N/A';
|
| 110 |
+
const attachmentUrl = report.attachmentUrl || '';
|
| 111 |
+
|
| 112 |
+
const statusClass = 'status-' + report.status.toLowerCase();
|
| 113 |
+
const statusText = getStatusText(report.status);
|
| 114 |
+
|
| 115 |
+
row.innerHTML =
|
| 116 |
+
'<td>' + employeeName + '</td>' +
|
| 117 |
+
'<td>' + report.title + '</td>' +
|
| 118 |
+
'<td>' + formattedDate + '</td>' +
|
| 119 |
+
'<td>' + (attachmentUrl ? '<a href="' + attachmentUrl + '" class="file-link" download>📎 Tải file</a>' : '<span>Không có</span>') + '</td>' +
|
| 120 |
+
'<td>' + (attachmentUrl ? '<a href="' + attachmentUrl + '" target="_blank" class="file-link">🔗 Mở link</a>' : '<span>Không có</span>') + '</td>' +
|
| 121 |
+
'<td><span class="' + statusClass + '">' + statusText + '</span></td>' +
|
| 122 |
+
'<td><div class="action-group"><form method="post"><button class="btn btn-success" type="submit">Duyệt</button></form><form method="post"><button class="btn btn-danger" type="submit">Từ chối</button></form></div></td>';
|
| 123 |
+
|
| 124 |
+
tableBody.appendChild(row);
|
| 125 |
+
});
|
| 126 |
+
} else {
|
| 127 |
+
tableBody.innerHTML = '<tr><td colspan="7" style="text-align:center">Không có báo cáo nào</td></tr>';
|
| 128 |
+
}
|
| 129 |
+
})
|
| 130 |
+
.catch(error => {
|
| 131 |
+
console.error('Error loading reports:', error);
|
| 132 |
+
tableBody.innerHTML = '<tr><td colspan="7" style="text-align:center">Lỗi khi tải danh sách báo cáo</td></tr>';
|
| 133 |
+
});
|
| 134 |
+
});
|
| 135 |
+
|
| 136 |
+
function getStatusText(status) {
|
| 137 |
+
if (status === 'PENDING') return 'Chờ duyệt';
|
| 138 |
+
if (status === 'APPROVED') return 'Đã duyệt';
|
| 139 |
+
if (status === 'REJECTED') return 'Từ chối';
|
| 140 |
+
return status;
|
| 141 |
+
}
|
| 142 |
+
</script>
|
| 143 |
+
</body>
|
| 144 |
+
</html>
|
src/main/resources/templates/cms/report/report-day.html
DELETED
|
@@ -1,144 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="vi">
|
| 3 |
-
|
| 4 |
-
<head>
|
| 5 |
-
<meta charset="UTF-8">
|
| 6 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
-
<title>Điểm danh hằng ngày</title>
|
| 8 |
-
<link rel="stylesheet" href="/css/report/report-day.css">
|
| 9 |
-
</head>
|
| 10 |
-
|
| 11 |
-
<body>
|
| 12 |
-
|
| 13 |
-
<div class="bg-image"></div>
|
| 14 |
-
|
| 15 |
-
<div class="layout">
|
| 16 |
-
|
| 17 |
-
<!-- Sidebar -->
|
| 18 |
-
<div th:replace="~{cms/sidebar/sidebar-role :: sidebar}"></div>
|
| 19 |
-
|
| 20 |
-
<!-- Content -->
|
| 21 |
-
<div class="container-layout">
|
| 22 |
-
|
| 23 |
-
<!-- Header -->
|
| 24 |
-
<div class="container-header">
|
| 25 |
-
<h1>Báo cáo hằng ngày</h1>
|
| 26 |
-
<p>Báo cáo điểm danh hằng ngày</p>
|
| 27 |
-
</div>
|
| 28 |
-
|
| 29 |
-
<!-- Card -->
|
| 30 |
-
<main class="main-container">
|
| 31 |
-
|
| 32 |
-
<div class="card-header">
|
| 33 |
-
<h2>Danh sách điểm danh</h2>
|
| 34 |
-
</div>
|
| 35 |
-
|
| 36 |
-
<!-- Bộ lọc -->
|
| 37 |
-
<div class="filter-section">
|
| 38 |
-
|
| 39 |
-
<div class="filter-group">
|
| 40 |
-
<label>Tên nhân viên</label>
|
| 41 |
-
<input type="text" placeholder="Nhập tên nhân viên...">
|
| 42 |
-
</div>
|
| 43 |
-
|
| 44 |
-
<div class="filter-group">
|
| 45 |
-
<label>Phòng ban</label>
|
| 46 |
-
<select>
|
| 47 |
-
<option>Tất cả</option>
|
| 48 |
-
<option>IT</option>
|
| 49 |
-
<option>Marketing</option>
|
| 50 |
-
<option>Nhân sự</option>
|
| 51 |
-
<option>Kế toán</option>
|
| 52 |
-
</select>
|
| 53 |
-
</div>
|
| 54 |
-
|
| 55 |
-
<div class="filter-group">
|
| 56 |
-
<label>Trạng thái</label>
|
| 57 |
-
<select>
|
| 58 |
-
<option>Tất cả</option>
|
| 59 |
-
<option>Có mặt</option>
|
| 60 |
-
<option>Đi muộn</option>
|
| 61 |
-
<option>Nghỉ</option>
|
| 62 |
-
</select>
|
| 63 |
-
</div>
|
| 64 |
-
|
| 65 |
-
<button class="filter-btn">
|
| 66 |
-
Tìm kiếm
|
| 67 |
-
</button>
|
| 68 |
-
|
| 69 |
-
</div>
|
| 70 |
-
|
| 71 |
-
<!-- Table -->
|
| 72 |
-
<div class="table-wrapper">
|
| 73 |
-
|
| 74 |
-
<table class="attendance-table">
|
| 75 |
-
|
| 76 |
-
<thead>
|
| 77 |
-
<tr>
|
| 78 |
-
<th>STT</th>
|
| 79 |
-
<th>Họ và tên</th>
|
| 80 |
-
<th>Phòng ban</th>
|
| 81 |
-
<th>Check In</th>
|
| 82 |
-
<th>Check Out</th>
|
| 83 |
-
<th>Trạng thái</th>
|
| 84 |
-
</tr>
|
| 85 |
-
</thead>
|
| 86 |
-
|
| 87 |
-
<tbody>
|
| 88 |
-
|
| 89 |
-
<tr>
|
| 90 |
-
<td>1</td>
|
| 91 |
-
<td>Nguyễn Văn A</td>
|
| 92 |
-
<td>IT</td>
|
| 93 |
-
<td>08:00</td>
|
| 94 |
-
<td>17:30</td>
|
| 95 |
-
<td>
|
| 96 |
-
<span class="status present">
|
| 97 |
-
Có mặt
|
| 98 |
-
</span>
|
| 99 |
-
</td>
|
| 100 |
-
</tr>
|
| 101 |
-
|
| 102 |
-
<tr>
|
| 103 |
-
<td>2</td>
|
| 104 |
-
<td>Trần Thị B</td>
|
| 105 |
-
<td>Marketing</td>
|
| 106 |
-
<td>08:25</td>
|
| 107 |
-
<td>17:30</td>
|
| 108 |
-
<td>
|
| 109 |
-
<span class="status late">
|
| 110 |
-
Đi muộn
|
| 111 |
-
</span>
|
| 112 |
-
</td>
|
| 113 |
-
</tr>
|
| 114 |
-
|
| 115 |
-
<tr>
|
| 116 |
-
<td>3</td>
|
| 117 |
-
<td>Lê Văn C</td>
|
| 118 |
-
<td>Nhân sự</td>
|
| 119 |
-
<td>--</td>
|
| 120 |
-
<td>--</td>
|
| 121 |
-
<td>
|
| 122 |
-
<span class="status absent">
|
| 123 |
-
Nghỉ
|
| 124 |
-
</span>
|
| 125 |
-
</td>
|
| 126 |
-
</tr>
|
| 127 |
-
|
| 128 |
-
</tbody>
|
| 129 |
-
|
| 130 |
-
</table>
|
| 131 |
-
|
| 132 |
-
</div>
|
| 133 |
-
|
| 134 |
-
</main>
|
| 135 |
-
<div th:replace="~{cms/layout/footer :: footer}"></div>
|
| 136 |
-
|
| 137 |
-
</div>
|
| 138 |
-
|
| 139 |
-
</div>
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
</body>
|
| 143 |
-
|
| 144 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/main/resources/templates/cms/report/report-month.html
DELETED
|
@@ -1,184 +0,0 @@
|
|
| 1 |
-
```html
|
| 2 |
-
<!DOCTYPE html>
|
| 3 |
-
<html lang="vi">
|
| 4 |
-
|
| 5 |
-
<head>
|
| 6 |
-
<meta charset="UTF-8">
|
| 7 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 8 |
-
<title>Báo cáo tháng</title>
|
| 9 |
-
<link rel="stylesheet" href="/css/report/report-month.css">
|
| 10 |
-
</head>
|
| 11 |
-
|
| 12 |
-
<body>
|
| 13 |
-
|
| 14 |
-
<div class="bg-image"></div>
|
| 15 |
-
|
| 16 |
-
<div class="layout">
|
| 17 |
-
|
| 18 |
-
<div th:replace="~{cms/sidebar/sidebar-role :: sidebar}"></div>
|
| 19 |
-
|
| 20 |
-
<div class="container-layout">
|
| 21 |
-
|
| 22 |
-
<div class="container-header">
|
| 23 |
-
<h1>Báo cáo hằng tháng</h1>
|
| 24 |
-
<p>Thống kê chuyên cần hằng tháng </p>
|
| 25 |
-
</div>
|
| 26 |
-
|
| 27 |
-
<main class="main-container">
|
| 28 |
-
|
| 29 |
-
<!-- Filter -->
|
| 30 |
-
<div class="filter-bar">
|
| 31 |
-
|
| 32 |
-
<div class="filter-group">
|
| 33 |
-
<label>Phòng ban</label>
|
| 34 |
-
<select>
|
| 35 |
-
<option>IT</option>
|
| 36 |
-
<option>Marketing</option>
|
| 37 |
-
<option>Nhân sự</option>
|
| 38 |
-
<option>Kế toán</option>
|
| 39 |
-
</select>
|
| 40 |
-
</div>
|
| 41 |
-
|
| 42 |
-
<div class="filter-group">
|
| 43 |
-
<label>Tháng</label>
|
| 44 |
-
<input type="month">
|
| 45 |
-
</div>
|
| 46 |
-
|
| 47 |
-
<button class="export-btn">
|
| 48 |
-
Xuất Excel
|
| 49 |
-
</button>
|
| 50 |
-
|
| 51 |
-
</div>
|
| 52 |
-
|
| 53 |
-
<!-- Overview -->
|
| 54 |
-
<div class="overview-grid">
|
| 55 |
-
|
| 56 |
-
<div class="stat-card">
|
| 57 |
-
<h3>Đi muộn</h3>
|
| 58 |
-
<span>23</span>
|
| 59 |
-
</div>
|
| 60 |
-
|
| 61 |
-
<div class="stat-card">
|
| 62 |
-
<h3>Đúng giờ</h3>
|
| 63 |
-
<span>154</span>
|
| 64 |
-
</div>
|
| 65 |
-
|
| 66 |
-
<div class="stat-card">
|
| 67 |
-
<h3>Về sớm</h3>
|
| 68 |
-
<span>12</span>
|
| 69 |
-
</div>
|
| 70 |
-
|
| 71 |
-
<div class="stat-card">
|
| 72 |
-
<h3>Nghỉ phép</h3>
|
| 73 |
-
<span>8</span>
|
| 74 |
-
</div>
|
| 75 |
-
|
| 76 |
-
<div class="stat-card">
|
| 77 |
-
<h3>Báo cáo đã nộp</h3>
|
| 78 |
-
<span>46</span>
|
| 79 |
-
</div>
|
| 80 |
-
|
| 81 |
-
</div>
|
| 82 |
-
|
| 83 |
-
<!-- Chart -->
|
| 84 |
-
<div class="chart-card">
|
| 85 |
-
|
| 86 |
-
<h2>Thống kê phòng ban</h2>
|
| 87 |
-
|
| 88 |
-
<div id="monthlyChart"></div>
|
| 89 |
-
|
| 90 |
-
</div>
|
| 91 |
-
|
| 92 |
-
<!-- Table -->
|
| 93 |
-
<div class="table-card">
|
| 94 |
-
|
| 95 |
-
<h2>Chi tiết thành viên</h2>
|
| 96 |
-
|
| 97 |
-
<table class="report-table">
|
| 98 |
-
|
| 99 |
-
<thead>
|
| 100 |
-
<tr>
|
| 101 |
-
<th>Tên</th>
|
| 102 |
-
<th>Phòng</th>
|
| 103 |
-
<th>Đi muộn</th>
|
| 104 |
-
<th>Đúng giờ</th>
|
| 105 |
-
<th>Về sớm</th>
|
| 106 |
-
<th>Nghỉ phép</th>
|
| 107 |
-
<th>Báo cáo nộp</th>
|
| 108 |
-
</tr>
|
| 109 |
-
</thead>
|
| 110 |
-
|
| 111 |
-
<tbody>
|
| 112 |
-
|
| 113 |
-
<tr>
|
| 114 |
-
<td>Nguyễn Văn A</td>
|
| 115 |
-
<td>IT</td>
|
| 116 |
-
<td>3</td>
|
| 117 |
-
<td>22</td>
|
| 118 |
-
<td>1</td>
|
| 119 |
-
<td>0</td>
|
| 120 |
-
<td>5</td>
|
| 121 |
-
</tr>
|
| 122 |
-
|
| 123 |
-
<tr>
|
| 124 |
-
<td>Trần Thị B</td>
|
| 125 |
-
<td>IT</td>
|
| 126 |
-
<td>1</td>
|
| 127 |
-
<td>24</td>
|
| 128 |
-
<td>0</td>
|
| 129 |
-
<td>1</td>
|
| 130 |
-
<td>4</td>
|
| 131 |
-
</tr>
|
| 132 |
-
|
| 133 |
-
</tbody>
|
| 134 |
-
|
| 135 |
-
</table>
|
| 136 |
-
|
| 137 |
-
</div>
|
| 138 |
-
|
| 139 |
-
</main>
|
| 140 |
-
<div th:replace="~{cms/layout/footer :: footer}"></div>
|
| 141 |
-
|
| 142 |
-
</div>
|
| 143 |
-
|
| 144 |
-
</div>
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
|
| 148 |
-
|
| 149 |
-
<script>
|
| 150 |
-
|
| 151 |
-
var options = {
|
| 152 |
-
chart: {
|
| 153 |
-
type: 'bar',
|
| 154 |
-
height: 350
|
| 155 |
-
},
|
| 156 |
-
|
| 157 |
-
series: [{
|
| 158 |
-
name: 'Số lần',
|
| 159 |
-
data: [23, 154, 12, 8, 46]
|
| 160 |
-
}],
|
| 161 |
-
|
| 162 |
-
xaxis: {
|
| 163 |
-
categories: [
|
| 164 |
-
'Đi muộn',
|
| 165 |
-
'Đúng giờ',
|
| 166 |
-
'Về sớm',
|
| 167 |
-
'Nghỉ phép',
|
| 168 |
-
'Báo cáo'
|
| 169 |
-
]
|
| 170 |
-
}
|
| 171 |
-
};
|
| 172 |
-
|
| 173 |
-
var chart = new ApexCharts(
|
| 174 |
-
document.querySelector("#monthlyChart"),
|
| 175 |
-
options
|
| 176 |
-
);
|
| 177 |
-
|
| 178 |
-
chart.render();
|
| 179 |
-
|
| 180 |
-
</script>
|
| 181 |
-
|
| 182 |
-
</body>
|
| 183 |
-
|
| 184 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/main/resources/templates/cms/sidebar/sidebar-employee.html
CHANGED
|
@@ -43,13 +43,13 @@
|
|
| 43 |
</a>
|
| 44 |
</li>
|
| 45 |
<li class="sidebar-option">
|
| 46 |
-
<a href="/
|
| 47 |
<span>📝</span>
|
| 48 |
-
|
| 49 |
</a>
|
| 50 |
</li>
|
| 51 |
<li class="sidebar-option">
|
| 52 |
-
<a href="/
|
| 53 |
<span>📄</span>
|
| 54 |
Nộp báo cáo
|
| 55 |
</a>
|
|
|
|
| 43 |
</a>
|
| 44 |
</li>
|
| 45 |
<li class="sidebar-option">
|
| 46 |
+
<a href="/report" class="sidebar-link">
|
| 47 |
<span>📝</span>
|
| 48 |
+
Danh sách báo cáo
|
| 49 |
</a>
|
| 50 |
</li>
|
| 51 |
<li class="sidebar-option">
|
| 52 |
+
<a href="/report/create" class="sidebar-link">
|
| 53 |
<span>📄</span>
|
| 54 |
Nộp báo cáo
|
| 55 |
</a>
|
src/main/resources/templates/cms/sidebar/sidebar-manage.html
CHANGED
|
@@ -54,7 +54,7 @@
|
|
| 54 |
</a>
|
| 55 |
</li>
|
| 56 |
<li class="sidebar-option">
|
| 57 |
-
<a href="/
|
| 58 |
<span>📝</span>
|
| 59 |
Danh sách báo cáo
|
| 60 |
</a>
|
|
|
|
| 54 |
</a>
|
| 55 |
</li>
|
| 56 |
<li class="sidebar-option">
|
| 57 |
+
<a href="/report" class="sidebar-link">
|
| 58 |
<span>📝</span>
|
| 59 |
Danh sách báo cáo
|
| 60 |
</a>
|
src/main/resources/templates/cms/user/user-information.html
CHANGED
|
@@ -61,7 +61,7 @@
|
|
| 61 |
</div>
|
| 62 |
|
| 63 |
<div class="info-item">
|
| 64 |
-
<label>
|
| 65 |
<p th:text="${#authentication.principal.department}">Không có </p>
|
| 66 |
</div>
|
| 67 |
|
|
@@ -148,12 +148,12 @@
|
|
| 148 |
<div class="error-message" th:if="${#fields.hasErrors('phone')}" th:errors="*{phone}"></div>
|
| 149 |
</div>
|
| 150 |
<div class="info-item">
|
| 151 |
-
<label >
|
| 152 |
-
<select name="
|
| 153 |
-
<option th:each="
|
| 154 |
-
th:value="${
|
| 155 |
-
th:text="${
|
| 156 |
-
th:selected="${#authentication.principal.department ==
|
| 157 |
</option>
|
| 158 |
</select>
|
| 159 |
</div>
|
|
|
|
| 61 |
</div>
|
| 62 |
|
| 63 |
<div class="info-item">
|
| 64 |
+
<label>Chuyên ngành</label>
|
| 65 |
<p th:text="${#authentication.principal.department}">Không có </p>
|
| 66 |
</div>
|
| 67 |
|
|
|
|
| 148 |
<div class="error-message" th:if="${#fields.hasErrors('phone')}" th:errors="*{phone}"></div>
|
| 149 |
</div>
|
| 150 |
<div class="info-item">
|
| 151 |
+
<label >Chuyên ngành</label>
|
| 152 |
+
<select name="specialization">
|
| 153 |
+
<option th:each="spec : ${T(com.attendenceSystem.module.user.entity.enums.Specialization).values()}"
|
| 154 |
+
th:value="${spec.name()}"
|
| 155 |
+
th:text="${spec.displayName}"
|
| 156 |
+
th:selected="${#authentication.principal.department == spec.name()}">
|
| 157 |
</option>
|
| 158 |
</select>
|
| 159 |
</div>
|