Spaces:
Configuration error
Configuration error
Fix some bug
Browse files- pom.xml +4 -0
- src/main/java/com/attendenceSystem/module/attendance/api/AttendanceApiController.java +5 -5
- src/main/java/com/attendenceSystem/module/attendance/api/AttendanceManagerApiController.java +4 -3
- src/main/java/com/attendenceSystem/module/user/entity/enums/Department.java +2 -2
- src/main/resources/static/js/employee/room-code.js +7 -8
- src/main/resources/templates/cms/absent/absent-list.html +2 -2
- src/main/resources/templates/cms/account/employee-list.html +20 -8
- src/main/resources/templates/cms/attendance/attendance-manual.html +11 -0
- src/main/resources/templates/cms/document/document-list.html +1 -1
- src/main/resources/templates/cms/face-id/faceID-create.html +18 -29
pom.xml
CHANGED
|
@@ -35,6 +35,10 @@
|
|
| 35 |
<groupId>org.springframework.boot</groupId>
|
| 36 |
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
| 37 |
</dependency> -->
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
<dependency>
|
| 39 |
<groupId>org.springframework.boot</groupId>
|
| 40 |
<artifactId>spring-boot-starter-mail</artifactId>
|
|
|
|
| 35 |
<groupId>org.springframework.boot</groupId>
|
| 36 |
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
| 37 |
</dependency> -->
|
| 38 |
+
<dependency>
|
| 39 |
+
<groupId>com.fasterxml.jackson.datatype</groupId>
|
| 40 |
+
<artifactId>jackson-datatype-jsr310</artifactId>
|
| 41 |
+
</dependency>
|
| 42 |
<dependency>
|
| 43 |
<groupId>org.springframework.boot</groupId>
|
| 44 |
<artifactId>spring-boot-starter-mail</artifactId>
|
src/main/java/com/attendenceSystem/module/attendance/api/AttendanceApiController.java
CHANGED
|
@@ -26,25 +26,25 @@ public class AttendanceApiController {
|
|
| 26 |
|
| 27 |
private final AttendanceService attendanceService;
|
| 28 |
|
| 29 |
-
@PostMapping(
|
| 30 |
public ResponseEntity<AttendanceResponse> checkIn() {
|
| 31 |
AttendanceResponse resp = attendanceService.checkIn();
|
| 32 |
return ResponseEntity.ok(resp);
|
| 33 |
}
|
| 34 |
|
| 35 |
-
@PostMapping(
|
| 36 |
public ResponseEntity<AttendanceResponse> checkOut() {
|
| 37 |
AttendanceResponse resp = attendanceService.checkOut();
|
| 38 |
return ResponseEntity.ok(resp);
|
| 39 |
}
|
| 40 |
|
| 41 |
-
@GetMapping(
|
| 42 |
public ResponseEntity<Page<AttendanceResponse>> getHistory(Pageable pageable) {
|
| 43 |
Page<AttendanceResponse> page = attendanceService.getAttendanceHistory(pageable);
|
| 44 |
return ResponseEntity.ok(page);
|
| 45 |
}
|
| 46 |
|
| 47 |
-
@PostMapping(
|
| 48 |
public ResponseEntity<LeaveRequestResponse> createLeave(@RequestBody CreateLeaveRequest request) {
|
| 49 |
LeaveRequestResponse resp = attendanceService.createLeaveRequest(request);
|
| 50 |
return ResponseEntity.ok(resp);
|
|
@@ -56,7 +56,7 @@ public class AttendanceApiController {
|
|
| 56 |
return ResponseEntity.ok(page);
|
| 57 |
}
|
| 58 |
|
| 59 |
-
@GetMapping("/
|
| 60 |
public ResponseEntity<LeaveDetailResponse> getLeaveDetail(@PathVariable Long id) {
|
| 61 |
LeaveDetailResponse detail = attendanceService.getLeaveDetail(id);
|
| 62 |
return ResponseEntity.ok(detail);
|
|
|
|
| 26 |
|
| 27 |
private final AttendanceService attendanceService;
|
| 28 |
|
| 29 |
+
@PostMapping(Routes.Attendance.CHECK_IN)
|
| 30 |
public ResponseEntity<AttendanceResponse> checkIn() {
|
| 31 |
AttendanceResponse resp = attendanceService.checkIn();
|
| 32 |
return ResponseEntity.ok(resp);
|
| 33 |
}
|
| 34 |
|
| 35 |
+
@PostMapping(Routes.Attendance.CHECK_OUT)
|
| 36 |
public ResponseEntity<AttendanceResponse> checkOut() {
|
| 37 |
AttendanceResponse resp = attendanceService.checkOut();
|
| 38 |
return ResponseEntity.ok(resp);
|
| 39 |
}
|
| 40 |
|
| 41 |
+
@GetMapping(Routes.Attendance.HISTORY)
|
| 42 |
public ResponseEntity<Page<AttendanceResponse>> getHistory(Pageable pageable) {
|
| 43 |
Page<AttendanceResponse> page = attendanceService.getAttendanceHistory(pageable);
|
| 44 |
return ResponseEntity.ok(page);
|
| 45 |
}
|
| 46 |
|
| 47 |
+
@PostMapping(Routes.Attendance.LEAVE)
|
| 48 |
public ResponseEntity<LeaveRequestResponse> createLeave(@RequestBody CreateLeaveRequest request) {
|
| 49 |
LeaveRequestResponse resp = attendanceService.createLeaveRequest(request);
|
| 50 |
return ResponseEntity.ok(resp);
|
|
|
|
| 56 |
return ResponseEntity.ok(page);
|
| 57 |
}
|
| 58 |
|
| 59 |
+
@GetMapping(Routes.Attendance.LEAVE_DETAIL + "/{id}")
|
| 60 |
public ResponseEntity<LeaveDetailResponse> getLeaveDetail(@PathVariable Long id) {
|
| 61 |
LeaveDetailResponse detail = attendanceService.getLeaveDetail(id);
|
| 62 |
return ResponseEntity.ok(detail);
|
src/main/java/com/attendenceSystem/module/attendance/api/AttendanceManagerApiController.java
CHANGED
|
@@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.RestController;
|
|
| 12 |
import com.attendenceSystem.constant.Routes;
|
| 13 |
import com.attendenceSystem.module.attendance.dto.response.AttendanceResponse;
|
| 14 |
import com.attendenceSystem.module.attendance.dto.response.ManagerStatsResponse;
|
|
|
|
| 15 |
import com.attendenceSystem.module.attendance.service.AttendanceService;
|
| 16 |
|
| 17 |
import lombok.RequiredArgsConstructor;
|
|
@@ -32,7 +33,7 @@ public class AttendanceManagerApiController {
|
|
| 32 |
com.attendenceSystem.module.attendance.entity.enums.AttendanceStatus attendanceStatus = null;
|
| 33 |
if (status != null && !status.isEmpty()) {
|
| 34 |
try {
|
| 35 |
-
attendanceStatus =
|
| 36 |
} catch (IllegalArgumentException e) {
|
| 37 |
// Invalid status, ignore
|
| 38 |
}
|
|
@@ -47,10 +48,10 @@ public class AttendanceManagerApiController {
|
|
| 47 |
@RequestParam(required = false) LocalDate startDate,
|
| 48 |
@RequestParam(required = false) LocalDate endDate,
|
| 49 |
@RequestParam(required = false) String status) {
|
| 50 |
-
|
| 51 |
if (status != null && !status.isEmpty()) {
|
| 52 |
try {
|
| 53 |
-
attendanceStatus =
|
| 54 |
} catch (IllegalArgumentException e) {
|
| 55 |
// Invalid status, ignore
|
| 56 |
}
|
|
|
|
| 12 |
import com.attendenceSystem.constant.Routes;
|
| 13 |
import com.attendenceSystem.module.attendance.dto.response.AttendanceResponse;
|
| 14 |
import com.attendenceSystem.module.attendance.dto.response.ManagerStatsResponse;
|
| 15 |
+
import com.attendenceSystem.module.attendance.entity.enums.AttendanceStatus;
|
| 16 |
import com.attendenceSystem.module.attendance.service.AttendanceService;
|
| 17 |
|
| 18 |
import lombok.RequiredArgsConstructor;
|
|
|
|
| 33 |
com.attendenceSystem.module.attendance.entity.enums.AttendanceStatus attendanceStatus = null;
|
| 34 |
if (status != null && !status.isEmpty()) {
|
| 35 |
try {
|
| 36 |
+
attendanceStatus = AttendanceStatus.valueOf(status);
|
| 37 |
} catch (IllegalArgumentException e) {
|
| 38 |
// Invalid status, ignore
|
| 39 |
}
|
|
|
|
| 48 |
@RequestParam(required = false) LocalDate startDate,
|
| 49 |
@RequestParam(required = false) LocalDate endDate,
|
| 50 |
@RequestParam(required = false) String status) {
|
| 51 |
+
AttendanceStatus attendanceStatus = null;
|
| 52 |
if (status != null && !status.isEmpty()) {
|
| 53 |
try {
|
| 54 |
+
attendanceStatus = AttendanceStatus.valueOf(status);
|
| 55 |
} catch (IllegalArgumentException e) {
|
| 56 |
// Invalid status, ignore
|
| 57 |
}
|
src/main/java/com/attendenceSystem/module/user/entity/enums/Department.java
CHANGED
|
@@ -7,8 +7,8 @@ import lombok.RequiredArgsConstructor;
|
|
| 7 |
@RequiredArgsConstructor
|
| 8 |
public enum Department {
|
| 9 |
DE213("DE213", "DE213-X7A9", "Phòng DE213"),
|
| 10 |
-
DE212("DE212", "DE212-
|
| 11 |
-
DE211("DE211", "DE211-
|
| 12 |
|
| 13 |
private final String roomCode;
|
| 14 |
private final String joinCode;
|
|
|
|
| 7 |
@RequiredArgsConstructor
|
| 8 |
public enum Department {
|
| 9 |
DE213("DE213", "DE213-X7A9", "Phòng DE213"),
|
| 10 |
+
DE212("DE212", "DE212-H3O4", "Phòng DE212"),
|
| 11 |
+
DE211("DE211", "DE211-FE2O3", "Phòng DE211");
|
| 12 |
|
| 13 |
private final String roomCode;
|
| 14 |
private final String joinCode;
|
src/main/resources/static/js/employee/room-code.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
const roomSelect = document.getElementById("roomSelect");
|
| 2 |
const roomCode = document.getElementById("roomCode");
|
| 3 |
|
| 4 |
-
const roomCodes = {
|
| 5 |
-
DE211: "DE211-X7A9",
|
| 6 |
-
DE212: "DE212-LP81",
|
| 7 |
-
DE213: "DE213-KM32"
|
| 8 |
-
};
|
| 9 |
-
|
| 10 |
roomSelect.addEventListener("change", () => {
|
| 11 |
-
roomCode.textContent =
|
| 12 |
-
roomCodes[roomSelect.value] || "--------";
|
| 13 |
});
|
|
|
|
| 1 |
+
// Lấy dữ liệu từ biến global đã được HTML (Thymeleaf) nạp sẵn
|
| 2 |
+
const roomCodes = window.serverRoomCodes || {};
|
| 3 |
+
|
| 4 |
+
console.log("Dữ liệu đồng bộ từ Backend:", roomCodes);
|
| 5 |
+
|
| 6 |
+
// Logic xử lý giao diện của FE giữ nguyên
|
| 7 |
const roomSelect = document.getElementById("roomSelect");
|
| 8 |
const roomCode = document.getElementById("roomCode");
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
roomSelect.addEventListener("change", () => {
|
| 11 |
+
roomCode.textContent = roomCodes[roomSelect.value] || "--------";
|
|
|
|
| 12 |
});
|
src/main/resources/templates/cms/absent/absent-list.html
CHANGED
|
@@ -131,7 +131,7 @@
|
|
| 131 |
</td>
|
| 132 |
|
| 133 |
|
| 134 |
-
|
| 135 |
<td>
|
| 136 |
<div class="action-group">
|
| 137 |
|
|
@@ -148,7 +148,7 @@
|
|
| 148 |
</form>
|
| 149 |
|
| 150 |
</div>
|
| 151 |
-
</td>
|
| 152 |
</tr>
|
| 153 |
|
| 154 |
</tbody>
|
|
|
|
| 131 |
</td>
|
| 132 |
|
| 133 |
|
| 134 |
+
|
| 135 |
<td>
|
| 136 |
<div class="action-group">
|
| 137 |
|
|
|
|
| 148 |
</form>
|
| 149 |
|
| 150 |
</div>
|
| 151 |
+
</td>
|
| 152 |
</tr>
|
| 153 |
|
| 154 |
</tbody>
|
src/main/resources/templates/cms/account/employee-list.html
CHANGED
|
@@ -51,7 +51,8 @@
|
|
| 51 |
|
| 52 |
<select>
|
| 53 |
<option>Tất cả phòng ban</option>
|
| 54 |
-
<option th:each="dept : ${departments}" th:value="${dept.name()}"
|
|
|
|
| 55 |
</select>
|
| 56 |
|
| 57 |
<select>
|
|
@@ -98,7 +99,8 @@
|
|
| 98 |
|
| 99 |
<div class="user">
|
| 100 |
|
| 101 |
-
<div class="avatar" th:text="${#strings.substring(user.fullName(), 0, 1)}">H
|
|
|
|
| 102 |
|
| 103 |
<strong th:text="${user.fullName()}">Nguyễn Minh Hải</strong>
|
| 104 |
|
|
@@ -130,10 +132,12 @@
|
|
| 130 |
Chi tiết
|
| 131 |
</a>
|
| 132 |
|
| 133 |
-
<form th:action="@{/manager/accounts/update/{id}(id=${user.id()})}"
|
|
|
|
| 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()}"
|
|
|
|
| 137 |
</select>
|
| 138 |
</form>
|
| 139 |
|
|
@@ -201,15 +205,23 @@
|
|
| 201 |
</div>
|
| 202 |
<button id="closePopup" class="close-btn">
|
| 203 |
<i class="fas fa-times">Đóng</i>
|
| 204 |
-
|
| 205 |
</button>
|
| 206 |
</div>
|
| 207 |
|
| 208 |
</div>
|
| 209 |
</section>
|
| 210 |
-
<script
|
| 211 |
-
|
| 212 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
</body>
|
| 214 |
|
| 215 |
</html>
|
|
|
|
| 51 |
|
| 52 |
<select>
|
| 53 |
<option>Tất cả phòng ban</option>
|
| 54 |
+
<option th:each="dept : ${departments}" th:value="${dept.name()}"
|
| 55 |
+
th:text="${dept.displayName}"></option>
|
| 56 |
</select>
|
| 57 |
|
| 58 |
<select>
|
|
|
|
| 99 |
|
| 100 |
<div class="user">
|
| 101 |
|
| 102 |
+
<div class="avatar" th:text="${#strings.substring(user.fullName(), 0, 1)}">H
|
| 103 |
+
</div>
|
| 104 |
|
| 105 |
<strong th:text="${user.fullName()}">Nguyễn Minh Hải</strong>
|
| 106 |
|
|
|
|
| 132 |
Chi tiết
|
| 133 |
</a>
|
| 134 |
|
| 135 |
+
<form th:action="@{/manager/accounts/update/{id}(id=${user.id()})}"
|
| 136 |
+
method="post" style="display:inline;">
|
| 137 |
<select name="department" onchange="this.form.submit()">
|
| 138 |
<option value="">Chuyển phòng</option>
|
| 139 |
+
<option th:each="dept : ${departments}" th:value="${dept.name()}"
|
| 140 |
+
th:text="${dept.displayName}"></option>
|
| 141 |
</select>
|
| 142 |
</form>
|
| 143 |
|
|
|
|
| 205 |
</div>
|
| 206 |
<button id="closePopup" class="close-btn">
|
| 207 |
<i class="fas fa-times">Đóng</i>
|
| 208 |
+
|
| 209 |
</button>
|
| 210 |
</div>
|
| 211 |
|
| 212 |
</div>
|
| 213 |
</section>
|
| 214 |
+
<script th:inline="javascript">
|
| 215 |
+
// Tạo một biến global để cầu nối sang file JS tĩnh
|
| 216 |
+
window.serverRoomCodes = {};
|
| 217 |
+
|
| 218 |
+
/*[# th:each="dept : ${T(com.attendenceSystem.module.user.entity.enums.Department).values()}"]*/
|
| 219 |
+
window.serverRoomCodes[[${ dept.roomCode }]] = [[${ dept.joinCode }]];
|
| 220 |
+
/*[/]*/
|
| 221 |
+
</script>
|
| 222 |
+
<script th:src="@{/js/employee/employee-popup.js}"></script>
|
| 223 |
+
<script th:src="@{/js/employee/js/room-code.js}"></script>
|
| 224 |
+
<script th:src="@{/js/employee/copy-code.js}"></script>
|
| 225 |
</body>
|
| 226 |
|
| 227 |
</html>
|
src/main/resources/templates/cms/attendance/attendance-manual.html
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Document</title>
|
| 7 |
+
</head>
|
| 8 |
+
<body>
|
| 9 |
+
|
| 10 |
+
</body>
|
| 11 |
+
</html>
|
src/main/resources/templates/cms/document/document-list.html
CHANGED
|
@@ -128,7 +128,7 @@
|
|
| 128 |
'<td>' + report.title + '</td>' +
|
| 129 |
'<td>' + formattedDate + '</td>' +
|
| 130 |
'<td>' + filesHtml + '</td>' +
|
| 131 |
-
'<td>' + linkHtml + '</td>'
|
| 132 |
|
| 133 |
tableBody.appendChild(row);
|
| 134 |
});
|
|
|
|
| 128 |
'<td>' + report.title + '</td>' +
|
| 129 |
'<td>' + formattedDate + '</td>' +
|
| 130 |
'<td>' + filesHtml + '</td>' +
|
| 131 |
+
'<td>' + linkHtml + '</td>'
|
| 132 |
|
| 133 |
tableBody.appendChild(row);
|
| 134 |
});
|
src/main/resources/templates/cms/face-id/faceID-create.html
CHANGED
|
@@ -40,19 +40,19 @@
|
|
| 40 |
|
| 41 |
<!-- THÔNG TIN -->
|
| 42 |
<div class="face-register-meta">
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
<h3>Thông tin đăng ký</h3>
|
| 45 |
|
| 46 |
-
<label for="roomCode">Mã phòng</label>
|
| 47 |
-
<input type="text"
|
| 48 |
-
id="roomCode"
|
| 49 |
-
placeholder="Ví dụ: P101">
|
| 50 |
-
|
| 51 |
<div class="face-sample-summary">
|
| 52 |
|
| 53 |
-
<button id="faceSampleCount"
|
| 54 |
-
class="face-sample-count-btn"
|
| 55 |
-
type="button">
|
| 56 |
0 / 5 mẫu
|
| 57 |
</button>
|
| 58 |
|
|
@@ -73,21 +73,18 @@
|
|
| 73 |
<div class="panel-header">
|
| 74 |
<h3>Camera đăng ký</h3>
|
| 75 |
|
| 76 |
-
<button id="faceRegisterToggleBtn"
|
| 77 |
-
type="button">
|
| 78 |
Bật camera
|
| 79 |
</button>
|
| 80 |
</div>
|
| 81 |
|
| 82 |
<!-- MODE -->
|
| 83 |
<div class="mode-tabs">
|
| 84 |
-
<button class="active"
|
| 85 |
-
type="button">
|
| 86 |
Đăng ký mới
|
| 87 |
</button>
|
| 88 |
|
| 89 |
-
<button type="button"
|
| 90 |
-
id="rescanBtn">
|
| 91 |
Quét lại
|
| 92 |
</button>
|
| 93 |
</div>
|
|
@@ -112,14 +109,10 @@
|
|
| 112 |
<!-- VIDEO -->
|
| 113 |
<div class="video-wrap">
|
| 114 |
|
| 115 |
-
<video id="video"
|
| 116 |
-
autoplay
|
| 117 |
-
playsinline
|
| 118 |
-
muted>
|
| 119 |
</video>
|
| 120 |
|
| 121 |
-
<canvas id="captureCanvas"
|
| 122 |
-
style="display:none;">
|
| 123 |
</canvas>
|
| 124 |
|
| 125 |
</div>
|
|
@@ -127,22 +120,18 @@
|
|
| 127 |
<!-- ACTION -->
|
| 128 |
<div class="action-row">
|
| 129 |
|
| 130 |
-
<button type="button"
|
| 131 |
-
id="captureFaceBtn">
|
| 132 |
📷 Chụp mẫu
|
| 133 |
</button>
|
| 134 |
|
| 135 |
-
<button type="button"
|
| 136 |
-
id="resetFaceScanBtn"
|
| 137 |
-
class="secondary">
|
| 138 |
Làm lại
|
| 139 |
</button>
|
| 140 |
|
| 141 |
</div>
|
| 142 |
|
| 143 |
<!-- MESSAGE -->
|
| 144 |
-
<div id="faceRegisterMessage"
|
| 145 |
-
class="message">
|
| 146 |
</div>
|
| 147 |
|
| 148 |
</div>
|
|
@@ -279,7 +268,7 @@
|
|
| 279 |
// Gửi ảnh lên server
|
| 280 |
async function submitSamples() {
|
| 281 |
const roomCode = document.getElementById('roomCode').value;
|
| 282 |
-
|
| 283 |
try {
|
| 284 |
const response = await fetch('/api/face-id/register', {
|
| 285 |
method: 'POST',
|
|
|
|
| 40 |
|
| 41 |
<!-- THÔNG TIN -->
|
| 42 |
<div class="face-register-meta">
|
| 43 |
+
<h3>Chuyển phòng nhanh</h3>
|
| 44 |
+
<h4>Lưu ý: Khi đã chuyển phòng</h4>
|
| 45 |
+
<div>
|
| 46 |
+
<input type="text" id="roomCode" placeholder="Nhập mã phòng (VD: P101)">
|
| 47 |
+
<button id="btnSwitchRoom" type="button">
|
| 48 |
+
Chuyển phòng
|
| 49 |
+
</button>
|
| 50 |
+
</div>
|
| 51 |
<h3>Thông tin đăng ký</h3>
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
<div class="face-sample-summary">
|
| 54 |
|
| 55 |
+
<button id="faceSampleCount" class="face-sample-count-btn" type="button">
|
|
|
|
|
|
|
| 56 |
0 / 5 mẫu
|
| 57 |
</button>
|
| 58 |
|
|
|
|
| 73 |
<div class="panel-header">
|
| 74 |
<h3>Camera đăng ký</h3>
|
| 75 |
|
| 76 |
+
<button id="faceRegisterToggleBtn" type="button">
|
|
|
|
| 77 |
Bật camera
|
| 78 |
</button>
|
| 79 |
</div>
|
| 80 |
|
| 81 |
<!-- MODE -->
|
| 82 |
<div class="mode-tabs">
|
| 83 |
+
<button class="active" type="button">
|
|
|
|
| 84 |
Đăng ký mới
|
| 85 |
</button>
|
| 86 |
|
| 87 |
+
<button type="button" id="rescanBtn">
|
|
|
|
| 88 |
Quét lại
|
| 89 |
</button>
|
| 90 |
</div>
|
|
|
|
| 109 |
<!-- VIDEO -->
|
| 110 |
<div class="video-wrap">
|
| 111 |
|
| 112 |
+
<video id="video" autoplay playsinline muted>
|
|
|
|
|
|
|
|
|
|
| 113 |
</video>
|
| 114 |
|
| 115 |
+
<canvas id="captureCanvas" style="display:none;">
|
|
|
|
| 116 |
</canvas>
|
| 117 |
|
| 118 |
</div>
|
|
|
|
| 120 |
<!-- ACTION -->
|
| 121 |
<div class="action-row">
|
| 122 |
|
| 123 |
+
<button type="button" id="captureFaceBtn">
|
|
|
|
| 124 |
📷 Chụp mẫu
|
| 125 |
</button>
|
| 126 |
|
| 127 |
+
<button type="button" id="resetFaceScanBtn" class="secondary">
|
|
|
|
|
|
|
| 128 |
Làm lại
|
| 129 |
</button>
|
| 130 |
|
| 131 |
</div>
|
| 132 |
|
| 133 |
<!-- MESSAGE -->
|
| 134 |
+
<div id="faceRegisterMessage" class="message">
|
|
|
|
| 135 |
</div>
|
| 136 |
|
| 137 |
</div>
|
|
|
|
| 268 |
// Gửi ảnh lên server
|
| 269 |
async function submitSamples() {
|
| 270 |
const roomCode = document.getElementById('roomCode').value;
|
| 271 |
+
|
| 272 |
try {
|
| 273 |
const response = await fetch('/api/face-id/register', {
|
| 274 |
method: 'POST',
|