QPAT commited on
Commit
2b09980
·
1 Parent(s): 1e37e16

remove roomCode in create face

Browse files
src/main/java/com/attendenceSystem/module/faceid/api/FaceIdApiController.java CHANGED
@@ -105,13 +105,17 @@ public class FaceIdApiController {
105
  @GetMapping("/latest")
106
  public ResponseEntity<LatestFaceResponse> getLatestFace() {
107
  try {
108
- User user = getCurrentUser();
109
- FaceProfile faceProfile = faceProfileRepository.findByUser(user)
110
  .orElseThrow(() -> new IllegalArgumentException("Chưa đăng ký face"));
111
 
112
  return ResponseEntity.ok(LatestFaceResponse.builder()
113
  .faceCode(faceProfile.getFaceCode())
114
  .imagePath(faceProfile.getThumbnailUrl())
 
 
 
 
115
  .build());
116
 
117
  } catch (IllegalArgumentException e) {
 
105
  @GetMapping("/latest")
106
  public ResponseEntity<LatestFaceResponse> getLatestFace() {
107
  try {
108
+ User currentUser = getCurrentUser();
109
+ FaceProfile faceProfile = faceProfileRepository.findByUser(currentUser)
110
  .orElseThrow(() -> new IllegalArgumentException("Chưa đăng ký face"));
111
 
112
  return ResponseEntity.ok(LatestFaceResponse.builder()
113
  .faceCode(faceProfile.getFaceCode())
114
  .imagePath(faceProfile.getThumbnailUrl())
115
+ .username(currentUser.getUsername())
116
+ .fullName(currentUser.getFullName())
117
+ .email(currentUser.getEmail())
118
+ .isAccept(faceProfile.getIsAccept())
119
  .build());
120
 
121
  } catch (IllegalArgumentException e) {
src/main/java/com/attendenceSystem/module/faceid/controller/FaceProfileController.java CHANGED
@@ -57,7 +57,7 @@ public class FaceProfileController {
57
  return Views.FaceId.CREATE;
58
  }
59
  faceProfileService.createFaceProfile(request);
60
- return Routes.REDIRECT + Routes.FaceId.ROOT;
61
  }
62
 
63
  @PostMapping(Routes.Action.ACCEPT + "/{id}")
 
57
  return Views.FaceId.CREATE;
58
  }
59
  faceProfileService.createFaceProfile(request);
60
+ return Routes.REDIRECT + Routes.FaceId.ROOT + Routes.Action.CREATE;
61
  }
62
 
63
  @PostMapping(Routes.Action.ACCEPT + "/{id}")
src/main/java/com/attendenceSystem/module/faceid/dto/request/FaceCaptureRequest.java CHANGED
@@ -15,5 +15,4 @@ import lombok.Setter;
15
  @Builder
16
  public class FaceCaptureRequest {
17
  private List<String> samples;
18
- private String roomCode;
19
- }
 
15
  @Builder
16
  public class FaceCaptureRequest {
17
  private List<String> samples;
18
+ }
 
src/main/java/com/attendenceSystem/module/faceid/dto/response/LatestFaceResponse.java CHANGED
@@ -6,5 +6,9 @@ import lombok.Builder;
6
  public record LatestFaceResponse(
7
  String faceCode,
8
  String imagePath,
9
- Long sampleId) {
10
- }
 
 
 
 
 
6
  public record LatestFaceResponse(
7
  String faceCode,
8
  String imagePath,
9
+ Long sampleId,
10
+ String username,
11
+ String fullName,
12
+ String email,
13
+ Boolean isAccept) {
14
+ }
src/main/resources/static/css/faceID/faceID-create.css CHANGED
@@ -81,39 +81,6 @@ video {
81
  font-weight: 700;
82
  }
83
 
84
- .room-code-field {
85
- display: flex;
86
- flex-direction: column;
87
- gap: 6px;
88
- margin-bottom: 12px;
89
- }
90
-
91
- .room-code-field label {
92
- color: var(--text-light);
93
- font-size: 0.9rem;
94
- font-weight: 600;
95
- }
96
-
97
- .room-code-field input {
98
- padding: 12px 14px;
99
- border-radius: 14px;
100
- border: 1px solid var(--border);
101
- background: var(--bg-form);
102
- color: var(--text-white);
103
- outline: none;
104
- transition: 0.25s;
105
- }
106
-
107
- .room-code-field input:focus {
108
- border-color: var(--indigo-06);
109
- box-shadow: 0 0 0 4px var(--indigo-15);
110
- }
111
-
112
- .room-code-field small {
113
- color: var(--text-gray);
114
- font-size: 0.8rem;
115
- }
116
-
117
  .face-sample-summary {
118
  display: flex;
119
  align-items: center;
@@ -364,6 +331,135 @@ video {
364
  box-shadow: 0 10px 25px rgba(99, 102, 241, 0.25);
365
  }
366
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  @media (max-width: 1100px) {
368
  .face-register-grid {
369
  grid-template-columns: 1fr;
 
81
  font-weight: 700;
82
  }
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  .face-sample-summary {
85
  display: flex;
86
  align-items: center;
 
331
  box-shadow: 0 10px 25px rgba(99, 102, 241, 0.25);
332
  }
333
 
334
+ /* ===================================
335
+ FACE DETAIL MODAL
336
+ =================================== */
337
+
338
+ .face-detail-action {
339
+ margin-top: 12px;
340
+ }
341
+
342
+ .detail-btn-secondary {
343
+ width: 100%;
344
+ padding: 10px 14px;
345
+ border-radius: 14px;
346
+ border: 1px solid var(--border);
347
+ background: var(--bg-table-06);
348
+ color: var(--text-light);
349
+ font-weight: 600;
350
+ cursor: pointer;
351
+ transition: 0.25s;
352
+ text-align: center;
353
+ }
354
+
355
+ .detail-btn-secondary:hover {
356
+ background: var(--indigo-15);
357
+ border-color: var(--indigo-06);
358
+ color: white;
359
+ }
360
+
361
+ .face-modal {
362
+ display: none;
363
+ position: fixed;
364
+ top: 0;
365
+ left: 0;
366
+ width: 100%;
367
+ height: 100%;
368
+ background: rgba(0, 0, 0, 0.75);
369
+ justify-content: center;
370
+ align-items: center;
371
+ z-index: 9999;
372
+ }
373
+
374
+ .face-modal.show {
375
+ display: flex;
376
+ }
377
+
378
+ .face-modal-content {
379
+ width: 700px;
380
+ max-width: 90%;
381
+ background: var(--bg-card);
382
+ border: 1px solid var(--border);
383
+ border-radius: 20px;
384
+ overflow: hidden;
385
+ box-shadow: 0 0 30px rgba(0, 0, 0, .4);
386
+ }
387
+
388
+ .face-modal-header {
389
+ display: flex;
390
+ justify-content: space-between;
391
+ align-items: center;
392
+ padding: 16px 20px;
393
+ border-bottom: 1px solid var(--border);
394
+ color: var(--text-white);
395
+ }
396
+
397
+ .face-modal-header h2 {
398
+ font-size: 1.1rem;
399
+ font-weight: 700;
400
+ }
401
+
402
+ .close-btn {
403
+ border: none;
404
+ background: transparent;
405
+ color: var(--text-white);
406
+ font-size: 22px;
407
+ cursor: pointer;
408
+ }
409
+
410
+ .face-gallery {
411
+ padding: 20px;
412
+ display: grid;
413
+ grid-template-columns: 1.3fr 1fr;
414
+ gap: 24px;
415
+ align-items: start;
416
+ min-height: 350px;
417
+ }
418
+
419
+ .face-gallery-image {
420
+ display: flex;
421
+ justify-content: center;
422
+ align-items: center;
423
+ }
424
+
425
+ .face-gallery-image img {
426
+ max-width: 400px;
427
+ width: 100%;
428
+ border-radius: 16px;
429
+ border: 1px solid var(--border);
430
+ object-fit: cover;
431
+ }
432
+
433
+ .face-detail-panel {
434
+ display: grid;
435
+ gap: 14px;
436
+ }
437
+
438
+ .face-detail-panel .detail-item {
439
+ background: rgba(255, 255, 255, 0.05);
440
+ border: 1px solid var(--border);
441
+ border-radius: 14px;
442
+ padding: 16px;
443
+ }
444
+
445
+ .face-detail-panel .detail-item span {
446
+ display: block;
447
+ font-size: 0.85rem;
448
+ color: var(--text-light);
449
+ margin-bottom: 6px;
450
+ }
451
+
452
+ .face-detail-panel .detail-item p {
453
+ margin: 0;
454
+ font-size: 1rem;
455
+ font-weight: 600;
456
+ color: var(--text-white);
457
+ }
458
+
459
+ .face-detail-panel .detail-item p#detailStatus {
460
+ color: var(--success);
461
+ }
462
+
463
  @media (max-width: 1100px) {
464
  .face-register-grid {
465
  grid-template-columns: 1fr;
src/main/resources/templates/cms/face-id/faceID-create.html CHANGED
@@ -45,20 +45,20 @@
45
  <span class="meta-badge">Bắt buộc</span>
46
  </div>
47
 
48
- <div class="room-code-field">
49
- <label for="roomCode">Mã phòng</label>
50
- <input type="text" id="roomCode" placeholder="VD: P101">
51
- <small>Nhập mã phòng trước khi bật camera và bắt đầu quét.</small>
52
- </div>
53
-
54
  <div class="face-sample-summary">
55
  <button id="faceSampleCount" class="face-sample-count-btn" type="button">
56
  0 / 5 mẫu
57
  </button>
58
  <span id="faceSampleStatusText">
59
- Chờ nhập phòng để bắt đầu.
60
  </span>
61
  </div>
 
 
 
 
 
 
62
  </div>
63
  </div>
64
 
@@ -155,6 +155,39 @@
155
 
156
  </div>
157
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  <script>
159
  const video = document.getElementById("video");
160
  const canvas = document.getElementById("captureCanvas");
@@ -162,7 +195,7 @@
162
  const captureBtn = document.getElementById("captureFaceBtn");
163
  const resetBtn = document.getElementById("resetFaceScanBtn");
164
  const rescanBtn = document.getElementById("rescanBtn");
165
- const roomCodeInput = document.getElementById("roomCode");
166
 
167
  const sampleCountBtn = document.getElementById("faceSampleCount");
168
  const sampleStatusText = document.getElementById("faceSampleStatusText");
@@ -296,18 +329,11 @@
296
  });
297
 
298
  captureBtn.addEventListener("click", () => {
299
- const roomCode = roomCodeInput.value.trim();
300
-
301
  if (!isStreaming) {
302
  setMessage("❌ Vui lòng bật camera trước.", "error");
303
  return;
304
  }
305
 
306
- if (!roomCode) {
307
- setMessage("❌ Vui lòng nhập mã phòng trước khi chụp.", "error");
308
- return;
309
- }
310
-
311
  if (capturedSamples.length >= 5) {
312
  setMessage("✅ Đã đủ 5 mẫu. Đang xử lý đăng ký.", "success");
313
  return;
@@ -338,16 +364,7 @@
338
  updateDetectionStatus("Đang chờ mẫu mới", "#3498db");
339
  });
340
 
341
- roomCodeInput.addEventListener("input", updateUI);
342
-
343
  async function submitSamples() {
344
- const roomCode = roomCodeInput.value.trim();
345
-
346
- if (!roomCode) {
347
- setMessage("❌ Vui lòng nhập mã phòng", "error");
348
- return;
349
- }
350
-
351
  try {
352
  const response = await fetch('/api/face-id/register', {
353
  method: 'POST',
@@ -355,7 +372,6 @@
355
  'Content-Type': 'application/json'
356
  },
357
  body: JSON.stringify({
358
- roomCode: roomCode,
359
  samples: capturedSamples.map(sample => sample.imageBase64)
360
  })
361
  });
@@ -364,9 +380,7 @@
364
  const result = await response.json();
365
  if (result.success) {
366
  setMessage(`✅ ${result.message}`, "success");
367
- setTimeout(() => {
368
- window.location.href = "/face-id";
369
- }, 2000);
370
  } else {
371
  setMessage(`❌ Lỗi: ${result.message}`, "error");
372
  }
@@ -381,14 +395,13 @@
381
 
382
  function updateUI() {
383
  const count = capturedSamples.length;
384
- const roomCode = roomCodeInput.value.trim();
385
 
386
  sampleCountBtn.textContent = `${count} / 5 mẫu`;
387
  sampleStatusText.textContent = count === 0
388
- ? (roomCode ? "Đã sẵn sàng để thu thập mẫu." : "Chờ nhập mã phòng để bắt đầu.")
389
  : `Đã thu thập ${count}/5 mẫu khuôn mặt`;
390
 
391
- captureBtn.disabled = !isStreaming || !roomCode || count >= 5;
392
  resetBtn.disabled = count === 0;
393
  rescanBtn.disabled = count === 0;
394
 
@@ -407,6 +420,55 @@
407
  : (count === 5 ? "Hoàn tất thu thập" : "Camera đang hoạt động");
408
  }
409
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
  updateUI();
411
  </script>
412
 
 
45
  <span class="meta-badge">Bắt buộc</span>
46
  </div>
47
 
 
 
 
 
 
 
48
  <div class="face-sample-summary">
49
  <button id="faceSampleCount" class="face-sample-count-btn" type="button">
50
  0 / 5 mẫu
51
  </button>
52
  <span id="faceSampleStatusText">
53
+ Chưa mẫu nào.
54
  </span>
55
  </div>
56
+
57
+ <div class="face-detail-action">
58
+ <button id="viewFaceDetailBtn" type="button" class="detail-btn-secondary">
59
+ 👤 Xem chi tiết khuôn mặt đã đăng ký
60
+ </button>
61
+ </div>
62
  </div>
63
  </div>
64
 
 
155
 
156
  </div>
157
 
158
+ <!-- Modal xem chi tiết khuôn mặt đã đăng ký -->
159
+ <div id="faceDetailModal" class="face-modal">
160
+ <div class="face-modal-content">
161
+ <div class="face-modal-header">
162
+ <h2>Chi tiết khuôn mặt đã đăng ký</h2>
163
+ <button class="close-btn" onclick="closeFaceDetailModal()">✕</button>
164
+ </div>
165
+ <div class="face-gallery">
166
+ <div class="face-gallery-image">
167
+ <img id="detailFaceImage" src="" alt="Face ID">
168
+ </div>
169
+ <div class="face-detail-panel">
170
+ <div class="detail-item">
171
+ <span>Mã sinh viên</span>
172
+ <p id="detailUsername">--</p>
173
+ </div>
174
+ <div class="detail-item">
175
+ <span>Họ tên</span>
176
+ <p id="detailFullName">--</p>
177
+ </div>
178
+ <div class="detail-item">
179
+ <span>Email</span>
180
+ <p id="detailEmail">--</p>
181
+ </div>
182
+ <div class="detail-item">
183
+ <span>Trạng thái</span>
184
+ <p id="detailStatus">--</p>
185
+ </div>
186
+ </div>
187
+ </div>
188
+ </div>
189
+ </div>
190
+
191
  <script>
192
  const video = document.getElementById("video");
193
  const canvas = document.getElementById("captureCanvas");
 
195
  const captureBtn = document.getElementById("captureFaceBtn");
196
  const resetBtn = document.getElementById("resetFaceScanBtn");
197
  const rescanBtn = document.getElementById("rescanBtn");
198
+ const viewDetailBtn = document.getElementById("viewFaceDetailBtn");
199
 
200
  const sampleCountBtn = document.getElementById("faceSampleCount");
201
  const sampleStatusText = document.getElementById("faceSampleStatusText");
 
329
  });
330
 
331
  captureBtn.addEventListener("click", () => {
 
 
332
  if (!isStreaming) {
333
  setMessage("❌ Vui lòng bật camera trước.", "error");
334
  return;
335
  }
336
 
 
 
 
 
 
337
  if (capturedSamples.length >= 5) {
338
  setMessage("✅ Đã đủ 5 mẫu. Đang xử lý đăng ký.", "success");
339
  return;
 
364
  updateDetectionStatus("Đang chờ mẫu mới", "#3498db");
365
  });
366
 
 
 
367
  async function submitSamples() {
 
 
 
 
 
 
 
368
  try {
369
  const response = await fetch('/api/face-id/register', {
370
  method: 'POST',
 
372
  'Content-Type': 'application/json'
373
  },
374
  body: JSON.stringify({
 
375
  samples: capturedSamples.map(sample => sample.imageBase64)
376
  })
377
  });
 
380
  const result = await response.json();
381
  if (result.success) {
382
  setMessage(`✅ ${result.message}`, "success");
383
+ // Không chuyển trang, chỉ hiển thị thành công
 
 
384
  } else {
385
  setMessage(`❌ Lỗi: ${result.message}`, "error");
386
  }
 
395
 
396
  function updateUI() {
397
  const count = capturedSamples.length;
 
398
 
399
  sampleCountBtn.textContent = `${count} / 5 mẫu`;
400
  sampleStatusText.textContent = count === 0
401
+ ? "Chưa mẫu nào."
402
  : `Đã thu thập ${count}/5 mẫu khuôn mặt`;
403
 
404
+ captureBtn.disabled = !isStreaming || count >= 5;
405
  resetBtn.disabled = count === 0;
406
  rescanBtn.disabled = count === 0;
407
 
 
420
  : (count === 5 ? "Hoàn tất thu thập" : "Camera đang hoạt động");
421
  }
422
 
423
+ // Xem chi tiết khuôn mặt đã đăng ký
424
+ viewDetailBtn.addEventListener("click", async () => {
425
+ try {
426
+ const response = await fetch('/api/face-id/latest');
427
+ if (response.ok) {
428
+ const data = await response.json();
429
+ document.getElementById("detailUsername").textContent = data.username || "--";
430
+ document.getElementById("detailFullName").textContent = data.fullName || "--";
431
+ document.getElementById("detailEmail").textContent = data.email || "--";
432
+
433
+ // Xác định trạng thái dựa trên isAccept
434
+ const statusEl = document.getElementById("detailStatus");
435
+ if (data.isAccept === true) {
436
+ statusEl.textContent = "Đã duyệt";
437
+ statusEl.style.color = "#2ecc71";
438
+ } else if (data.isAccept === false) {
439
+ statusEl.textContent = "Bị từ chối";
440
+ statusEl.style.color = "#e74c3c";
441
+ } else {
442
+ statusEl.textContent = "Chờ duyệt";
443
+ statusEl.style.color = "#f0ad4e";
444
+ }
445
+
446
+ document.getElementById("detailFaceImage").src = data.imagePath || "https://i.pinimg.com/736x/6a/5e/fc/6a5efc25bf7c7603afac79d7046f999c.jpg";
447
+ openFaceDetailModal();
448
+ } else {
449
+ setMessage("❌ Bạn chưa đăng ký khuôn mặt nào.", "error");
450
+ }
451
+ } catch (error) {
452
+ console.error(error);
453
+ setMessage("❌ Lỗi kết nối khi lấy thông tin.", "error");
454
+ }
455
+ });
456
+
457
+ function openFaceDetailModal() {
458
+ document.getElementById("faceDetailModal").classList.add("show");
459
+ }
460
+
461
+ function closeFaceDetailModal() {
462
+ document.getElementById("faceDetailModal").classList.remove("show");
463
+ }
464
+
465
+ window.onclick = function (event) {
466
+ const modal = document.getElementById("faceDetailModal");
467
+ if (event.target === modal) {
468
+ closeFaceDetailModal();
469
+ }
470
+ };
471
+
472
  updateUI();
473
  </script>
474
 
uploads/face_samples/11/face_11_1.jpg ADDED
uploads/face_samples/11/face_11_2.jpg ADDED
uploads/face_samples/11/face_11_3.jpg ADDED
uploads/face_samples/11/face_11_4.jpg ADDED
uploads/face_samples/11/face_11_5.jpg ADDED
uploads/face_samples/4/face_4_1.jpg CHANGED
uploads/face_samples/4/face_4_2.jpg CHANGED
uploads/face_samples/4/face_4_3.jpg CHANGED
uploads/face_samples/4/face_4_4.jpg CHANGED
uploads/face_samples/4/face_4_5.jpg CHANGED