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

Add detail in report

Browse files
src/main/java/com/attendenceSystem/module/report/dto/response/ReportDetailResponse.java CHANGED
@@ -1,6 +1,7 @@
1
  package com.attendenceSystem.module.report.dto.response;
2
 
3
  import java.time.LocalDateTime;
 
4
 
5
  import com.attendenceSystem.module.report.entity.enums.ReportStatus;
6
 
@@ -14,5 +15,6 @@ public record ReportDetailResponse(
14
  ReportStatus status,
15
  String rejectReason,
16
  LocalDateTime createdAt,
17
- LocalDateTime reviewedAt) {
 
18
  }
 
1
  package com.attendenceSystem.module.report.dto.response;
2
 
3
  import java.time.LocalDateTime;
4
+ import java.util.List;
5
 
6
  import com.attendenceSystem.module.report.entity.enums.ReportStatus;
7
 
 
15
  ReportStatus status,
16
  String rejectReason,
17
  LocalDateTime createdAt,
18
+ LocalDateTime reviewedAt,
19
+ List<String> sharedUserNames) {
20
  }
src/main/java/com/attendenceSystem/module/report/entity/Report.java CHANGED
@@ -7,6 +7,8 @@ import org.hibernate.annotations.CreationTimestamp;
7
  import com.attendenceSystem.module.report.entity.enums.ReportStatus;
8
  import com.attendenceSystem.module.user.entity.User;
9
 
 
 
10
  import jakarta.persistence.Column;
11
  import jakarta.persistence.Entity;
12
  import jakarta.persistence.FetchType;
@@ -16,6 +18,7 @@ import jakarta.persistence.Id;
16
  import jakarta.persistence.JoinColumn;
17
  import jakarta.persistence.Lob;
18
  import jakarta.persistence.ManyToOne;
 
19
  import jakarta.persistence.Table;
20
 
21
  import lombok.AllArgsConstructor;
@@ -66,4 +69,7 @@ public class Report {
66
 
67
  @Column(name = "reviewed_at")
68
  private LocalDateTime reviewedAt;
69
- }
 
 
 
 
7
  import com.attendenceSystem.module.report.entity.enums.ReportStatus;
8
  import com.attendenceSystem.module.user.entity.User;
9
 
10
+ import java.util.List;
11
+
12
  import jakarta.persistence.Column;
13
  import jakarta.persistence.Entity;
14
  import jakarta.persistence.FetchType;
 
18
  import jakarta.persistence.JoinColumn;
19
  import jakarta.persistence.Lob;
20
  import jakarta.persistence.ManyToOne;
21
+ import jakarta.persistence.OneToMany;
22
  import jakarta.persistence.Table;
23
 
24
  import lombok.AllArgsConstructor;
 
69
 
70
  @Column(name = "reviewed_at")
71
  private LocalDateTime reviewedAt;
72
+
73
+ @OneToMany(mappedBy = "report", fetch = FetchType.LAZY)
74
+ private List<ReportShare> shares;
75
+ }
src/main/java/com/attendenceSystem/module/report/mapper/response/ReportDetailResponseMapper.java CHANGED
@@ -1,7 +1,12 @@
1
  package com.attendenceSystem.module.report.mapper.response;
2
 
 
 
 
 
3
  import com.attendenceSystem.module.report.dto.response.ReportDetailResponse;
4
  import com.attendenceSystem.module.report.entity.Report;
 
5
 
6
  import lombok.AccessLevel;
7
  import lombok.NoArgsConstructor;
@@ -10,6 +15,14 @@ import lombok.NoArgsConstructor;
10
  public class ReportDetailResponseMapper {
11
 
12
  public static ReportDetailResponse fromEntity(Report report) {
 
 
 
 
 
 
 
 
13
  return new ReportDetailResponse(
14
  report.getId(),
15
  report.getTitle(),
@@ -20,7 +33,8 @@ public class ReportDetailResponseMapper {
20
  report.getStatus(),
21
  report.getRejectReason(),
22
  report.getCreatedAt(),
23
- report.getReviewedAt()
 
24
  );
25
  }
26
- }
 
1
  package com.attendenceSystem.module.report.mapper.response;
2
 
3
+ import java.util.Collections;
4
+ import java.util.List;
5
+ import java.util.stream.Collectors;
6
+
7
  import com.attendenceSystem.module.report.dto.response.ReportDetailResponse;
8
  import com.attendenceSystem.module.report.entity.Report;
9
+ import com.attendenceSystem.module.report.entity.ReportShare;
10
 
11
  import lombok.AccessLevel;
12
  import lombok.NoArgsConstructor;
 
15
  public class ReportDetailResponseMapper {
16
 
17
  public static ReportDetailResponse fromEntity(Report report) {
18
+ List<String> sharedUserNames = Collections.emptyList();
19
+ if (report.getShares() != null) {
20
+ sharedUserNames = report.getShares().stream()
21
+ .map(ReportShare::getUser)
22
+ .map(user -> user.getFullName())
23
+ .collect(Collectors.toList());
24
+ }
25
+
26
  return new ReportDetailResponse(
27
  report.getId(),
28
  report.getTitle(),
 
33
  report.getStatus(),
34
  report.getRejectReason(),
35
  report.getCreatedAt(),
36
+ report.getReviewedAt(),
37
+ sharedUserNames
38
  );
39
  }
40
+ }
src/main/java/com/attendenceSystem/module/report/repository/ReportRepository.java CHANGED
@@ -1,8 +1,12 @@
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.stereotype.Repository;
7
 
8
  import com.attendenceSystem.module.report.entity.Report;
@@ -15,4 +19,7 @@ public interface ReportRepository extends JpaRepository<Report, Long> {
15
  long countByEmployeeAndStatus(User employee, ReportStatus status);
16
  Page<Report> findByEmployeeOrderByCreatedAtDesc(User employee, Pageable pageable);
17
  Page<Report> findAllByOrderByCreatedAtDesc(Pageable pageable);
18
- }
 
 
 
 
1
  package com.attendenceSystem.module.report.repository;
2
 
3
+ import java.util.Optional;
4
+
5
  import org.springframework.data.domain.Page;
6
  import org.springframework.data.domain.Pageable;
7
  import org.springframework.data.jpa.repository.JpaRepository;
8
+ import org.springframework.data.jpa.repository.Query;
9
+ import org.springframework.data.repository.query.Param;
10
  import org.springframework.stereotype.Repository;
11
 
12
  import com.attendenceSystem.module.report.entity.Report;
 
19
  long countByEmployeeAndStatus(User employee, ReportStatus status);
20
  Page<Report> findByEmployeeOrderByCreatedAtDesc(User employee, Pageable pageable);
21
  Page<Report> findAllByOrderByCreatedAtDesc(Pageable pageable);
22
+
23
+ @Query("SELECT r FROM Report r LEFT JOIN FETCH r.shares rs LEFT JOIN FETCH rs.user WHERE r.id = :id")
24
+ Optional<Report> findByIdWithShares(@Param("id") Long id);
25
+ }
src/main/java/com/attendenceSystem/module/report/service/impl/ReportServiceImpl.java CHANGED
@@ -107,7 +107,7 @@ public class ReportServiceImpl implements ReportService {
107
 
108
  @Override
109
  public ReportDetailResponse getReportById(final Long id) {
110
- Report report = reportRepository.findById(id)
111
  .orElseThrow(() -> new ResourceNotFoundException("Không tìm thấy báo cáo với id: " + id));
112
  return ReportDetailResponseMapper.fromEntity(report);
113
  }
 
107
 
108
  @Override
109
  public ReportDetailResponse getReportById(final Long id) {
110
+ Report report = reportRepository.findByIdWithShares(id)
111
  .orElseThrow(() -> new ResourceNotFoundException("Không tìm thấy báo cáo với id: " + id));
112
  return ReportDetailResponseMapper.fromEntity(report);
113
  }
src/main/resources/static/css/document/document-list.css CHANGED
@@ -1,5 +1,114 @@
1
  @import url("../base.css");
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  .document-card {
5
  background: var(--bg-card);
 
1
  @import url("../base.css");
2
 
3
+ /* ==========================
4
+ MODAL
5
+ ========================== */
6
+
7
+ .modal {
8
+ display: none;
9
+ position: fixed;
10
+ z-index: 1000;
11
+ left: 0;
12
+ top: 0;
13
+ width: 100%;
14
+ height: 100%;
15
+ background: rgba(0, 0, 0, 0.6);
16
+ backdrop-filter: blur(4px);
17
+ justify-content: center;
18
+ align-items: center;
19
+ }
20
+
21
+ .modal-content {
22
+ background: var(--bg-card);
23
+ backdrop-filter: blur(20px);
24
+ border: 1px solid var(--border);
25
+ border-radius: 24px;
26
+ padding: 32px;
27
+ max-width: 600px;
28
+ width: 90%;
29
+ max-height: 80vh;
30
+ overflow-y: auto;
31
+ box-shadow: 0 20px 60px rgba(0, 0, 0, .4);
32
+ position: relative;
33
+ }
34
+
35
+ .modal-content h2 {
36
+ font-size: 1.4rem;
37
+ font-weight: 700;
38
+ margin-bottom: 20px;
39
+ color: var(--text-white);
40
+ padding-right: 30px;
41
+ }
42
+
43
+ .modal-content p {
44
+ margin-bottom: 12px;
45
+ color: var(--text-light);
46
+ font-size: .95rem;
47
+ line-height: 1.5;
48
+ }
49
+
50
+ .modal-content p strong {
51
+ color: var(--text-white);
52
+ display: inline-block;
53
+ min-width: 140px;
54
+ }
55
+
56
+ .modal-content .report-detail,
57
+ .modal-content .report-shared-users,
58
+ .modal-content .report-attachments,
59
+ .modal-content .report-link {
60
+ margin-bottom: 16px;
61
+ padding: 12px 16px;
62
+ background: rgba(255, 255, 255, .04);
63
+ border-radius: 12px;
64
+ border: 1px solid var(--border);
65
+ }
66
+
67
+ .modal-content .report-detail strong,
68
+ .modal-content .report-shared-users strong,
69
+ .modal-content .report-attachments strong,
70
+ .modal-content .report-link strong {
71
+ display: block;
72
+ margin-bottom: 8px;
73
+ color: var(--text-white);
74
+ font-size: .9rem;
75
+ }
76
+
77
+ .modal-content .report-detail p,
78
+ .modal-content .report-shared-users div,
79
+ .modal-content .report-attachments div,
80
+ .modal-content .report-link div {
81
+ color: var(--text-light);
82
+ font-size: .9rem;
83
+ line-height: 1.6;
84
+ }
85
+
86
+ .shared-user-tag {
87
+ display: inline-block;
88
+ background: rgba(99, 102, 241, .15);
89
+ color: var(--indigo-06);
90
+ padding: 4px 12px;
91
+ border-radius: 999px;
92
+ font-size: .85rem;
93
+ font-weight: 500;
94
+ margin: 2px 4px;
95
+ }
96
+
97
+ .close-btn {
98
+ position: absolute;
99
+ top: 16px;
100
+ right: 20px;
101
+ font-size: 1.8rem;
102
+ color: var(--text-gray);
103
+ cursor: pointer;
104
+ transition: .25s;
105
+ line-height: 1;
106
+ }
107
+
108
+ .close-btn:hover {
109
+ color: var(--text-white);
110
+ transform: rotate(90deg);
111
+ }
112
 
113
  .document-card {
114
  background: var(--bg-card);
src/main/resources/templates/cms/document/document-detail.html CHANGED
@@ -1,4 +1,4 @@
1
- <div id="reportModal" class="modal">
2
 
3
  <div class="modal-content">
4
 
@@ -7,7 +7,7 @@
7
  <h2 id="modalTitle"></h2>
8
 
9
  <p>
10
- <strong>Nhân viên:</strong>
11
  <span id="modalEmployee"></span>
12
  </p>
13
 
@@ -21,13 +21,18 @@
21
  <p id="modalContent"></p>
22
  </div>
23
 
 
 
 
 
 
24
  <div class="report-attachments">
25
- <strong>File đính kèm:</strong>
26
  <div id="modalFiles"></div>
27
  </div>
28
 
29
  <div class="report-link">
30
- <strong>Link tài liệu:</strong>
31
  <div id="modalLink"></div>
32
  </div>
33
 
 
1
+ <div id="reportModal" class="modal" th:fragment="reportModal">
2
 
3
  <div class="modal-content">
4
 
 
7
  <h2 id="modalTitle"></h2>
8
 
9
  <p>
10
+ <strong>Người chia sẻ:</strong>
11
  <span id="modalEmployee"></span>
12
  </p>
13
 
 
21
  <p id="modalContent"></p>
22
  </div>
23
 
24
+ <div class="report-shared-users">
25
+ <strong>Người được chia sẻ:</strong>
26
+ <div id="modalSharedUsers"></div>
27
+ </div>
28
+
29
  <div class="report-attachments">
30
+ <strong>Tập đính kèm:</strong>
31
  <div id="modalFiles"></div>
32
  </div>
33
 
34
  <div class="report-link">
35
+ <strong>Liên kết:</strong>
36
  <div id="modalLink"></div>
37
  </div>
38
 
src/main/resources/templates/cms/document/document-list.html CHANGED
@@ -35,13 +35,13 @@
35
  <th>Ngày nộp</th>
36
  <th>File đính kèm</th>
37
  <th>Link tài liệu</th>
38
-
39
  </tr>
40
  </thead>
41
 
42
  <tbody id="reportTableBody">
43
  <tr>
44
- <td colspan="5" style="text-align:center">Đang tải...</td>
45
  </tr>
46
  </tbody>
47
  </table>
@@ -69,6 +69,9 @@
69
  </div>
70
  </div>
71
 
 
 
 
72
  <script>
73
  document.addEventListener('DOMContentLoaded', function() {
74
  const tableBody = document.getElementById('reportTableBody');
@@ -129,19 +132,95 @@
129
  '<td>' + report.title + '</td>' +
130
  '<td>' + formattedDate + '</td>' +
131
  '<td>' + filesHtml + '</td>' +
132
- '<td>' + linkHtml + '</td>';
 
133
 
134
  tableBody.appendChild(row);
135
  });
 
 
 
 
 
 
 
 
136
  } else {
137
- tableBody.innerHTML = '<tr><td colspan="5" style="text-align:center">Không có báo cáo nào</td></tr>';
138
  }
139
  })
140
  .catch(error => {
141
  console.error('Error loading reports:', error);
142
- tableBody.innerHTML = '<tr><td colspan="5" style="text-align:center">Lỗi khi tải danh sách báo cáo</td></tr>';
143
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  });
145
  </script>
146
  </body>
147
- </html>
 
35
  <th>Ngày nộp</th>
36
  <th>File đính kèm</th>
37
  <th>Link tài liệu</th>
38
+ <th>Hành động</th>
39
  </tr>
40
  </thead>
41
 
42
  <tbody id="reportTableBody">
43
  <tr>
44
+ <td colspan="6" style="text-align:center">Đang tải...</td>
45
  </tr>
46
  </tbody>
47
  </table>
 
69
  </div>
70
  </div>
71
 
72
+ <!-- Modal chi tiết báo cáo -->
73
+ <div th:replace="~{cms/document/document-detail :: reportModal}"></div>
74
+
75
  <script>
76
  document.addEventListener('DOMContentLoaded', function() {
77
  const tableBody = document.getElementById('reportTableBody');
 
132
  '<td>' + report.title + '</td>' +
133
  '<td>' + formattedDate + '</td>' +
134
  '<td>' + filesHtml + '</td>' +
135
+ '<td>' + linkHtml + '</td>' +
136
+ '<td><button class="btn btn-detail" data-id="' + report.id + '">Chi tiết</button></td>';
137
 
138
  tableBody.appendChild(row);
139
  });
140
+
141
+ // Gắn sự kiện click cho các nút Chi tiết
142
+ document.querySelectorAll('.btn-detail').forEach(button => {
143
+ button.addEventListener('click', function() {
144
+ const reportId = this.getAttribute('data-id');
145
+ openReportDetail(reportId);
146
+ });
147
+ });
148
  } else {
149
+ tableBody.innerHTML = '<tr><td colspan="6" style="text-align:center">Không có báo cáo nào</td></tr>';
150
  }
151
  })
152
  .catch(error => {
153
  console.error('Error loading reports:', error);
154
+ tableBody.innerHTML = '<tr><td colspan="6" style="text-align:center">Lỗi khi tải danh sách báo cáo</td></tr>';
155
  });
156
+
157
+ // Modal functions
158
+ const modal = document.getElementById('reportModal');
159
+ const closeBtn = modal.querySelector('.close-btn');
160
+
161
+ closeBtn.onclick = function() {
162
+ modal.style.display = 'none';
163
+ };
164
+
165
+ window.onclick = function(event) {
166
+ if (event.target === modal) {
167
+ modal.style.display = 'none';
168
+ }
169
+ };
170
+
171
+ async function openReportDetail(reportId) {
172
+ try {
173
+ const response = await fetch('/api/report/' + reportId);
174
+ if (!response.ok) {
175
+ throw new Error('Không thể tải chi tiết báo cáo');
176
+ }
177
+ const data = await response.json();
178
+
179
+ document.getElementById('modalTitle').textContent = data.title || '--';
180
+ document.getElementById('modalEmployee').textContent = data.employeeName || '--';
181
+
182
+ const date = data.createdAt ? new Date(data.createdAt) : null;
183
+ document.getElementById('modalDate').textContent = date ? date.toLocaleDateString('vi-VN') : '--';
184
+
185
+ document.getElementById('modalContent').textContent = data.content || '--';
186
+
187
+ // Hiển thị người được chia sẻ
188
+ const sharedUsersDiv = document.getElementById('modalSharedUsers');
189
+ if (data.sharedUserNames && data.sharedUserNames.length > 0) {
190
+ sharedUsersDiv.innerHTML = data.sharedUserNames.map(name =>
191
+ '<span class="shared-user-tag">' + name + '</span>'
192
+ ).join(' ');
193
+ } else {
194
+ sharedUsersDiv.innerHTML = '<span>Không có</span>';
195
+ }
196
+
197
+ // Hiển thị tập đính kèm
198
+ const filesDiv = document.getElementById('modalFiles');
199
+ if (data.attachmentFiles) {
200
+ const filePaths = data.attachmentFiles.split(',');
201
+ filesDiv.innerHTML = filePaths.map(path => {
202
+ const filename = path.split('/').pop();
203
+ return '<a href="' + path + '" class="file-link" download>📎 ' + filename + '</a>';
204
+ }).join('<br>');
205
+ } else {
206
+ filesDiv.innerHTML = '<span>Không có</span>';
207
+ }
208
+
209
+ // Hiển thị liên kết
210
+ const linkDiv = document.getElementById('modalLink');
211
+ if (data.attachmentUrl) {
212
+ linkDiv.innerHTML = '<a href="' + data.attachmentUrl + '" target="_blank" class="file-link">🔗 Mở link</a>';
213
+ } else {
214
+ linkDiv.innerHTML = '<span>Không có</span>';
215
+ }
216
+
217
+ modal.style.display = 'flex';
218
+ } catch (error) {
219
+ console.error('Error loading report detail:', error);
220
+ alert('Lỗi khi tải chi tiết báo cáo');
221
+ }
222
+ }
223
  });
224
  </script>
225
  </body>
226
+ </html>
src/main/resources/templates/cms/sidebar/sidebar-admin.html CHANGED
@@ -18,7 +18,7 @@
18
  <li class="sidebar-option">
19
  <a href="/dashboard/admin" class="sidebar-link">
20
  <span>📊</span>
21
- Dashboard
22
  </a>
23
  </li>
24
 
 
18
  <li class="sidebar-option">
19
  <a href="/dashboard/admin" class="sidebar-link">
20
  <span>📊</span>
21
+ Tổng quan
22
  </a>
23
  </li>
24
 
src/main/resources/templates/cms/sidebar/sidebar-employee.html CHANGED
@@ -18,7 +18,7 @@
18
  <li class="sidebar-option">
19
  <a href="/dashboard/employee" class="sidebar-link">
20
  <span>📊</span>
21
- Dashboard
22
  </a>
23
  </li>
24
 
 
18
  <li class="sidebar-option">
19
  <a href="/dashboard/employee" class="sidebar-link">
20
  <span>📊</span>
21
+ Tổng quan
22
  </a>
23
  </li>
24
 
src/main/resources/templates/cms/sidebar/sidebar-manage.html CHANGED
@@ -24,7 +24,7 @@
24
  <li class="sidebar-option">
25
  <a href="/dashboard/manager" class="sidebar-link">
26
  <span>📊</span>
27
- Dashboard
28
  </a>
29
  </li>
30
 
 
24
  <li class="sidebar-option">
25
  <a href="/dashboard/manager" class="sidebar-link">
26
  <span>📊</span>
27
+ Tổng quan
28
  </a>
29
  </li>
30