이 페이지는 관리자가 유저의 정보와 활동을 관리할 수 있는 페이지이다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!-- 헤드 레이아웃 적용 -->
<head th:insert="~{layout :: head}"></head>
<head>
<!-- 관리자회원삭제 -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="/js/admin.js"></script>
</head>
<body>
<!-- 네비게이션 메뉴 레이아웃 적용 -->
<div th:replace="~{layout :: nav}"></div>
<!-- 헤더 레이아웃 적용 -->
<div th:replace="~{layout :: header}"></div>
<!-- 컨텐츠(이 부분이 변경 됩니다.) -->
<div class="index-contents">
<div class="container">
<div class="container-container1">
<span class="container-text"><span>User List - ADMIN ONLY</span></span>
<table class="table table-striped">
<thead>
<tr>
<th>UserId</th>
<th>User Name</th>
<th>User Role</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${users}">
<td><input class="form-control" type="text" th:value="${user.userId}" name="userId" readonly/></td>
<td><input class="form-control" type="text" th:value="${user.username}" name="username" readonly/></td>
<td><input class="form-control" type="text" th:value="${user.role}" name="role" readonly/></td>
<td><button type="button" class="btn btn-outline-danger" th:onclick="'deleteUser(\'' + ${user.userId} + '\')'">Delete User</button></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 푸터 레이아웃 적용 -->
<div th:replace="~{layout :: footer}"></div>
</body>
</html>
$(document).ready(function () {
window.deleteUser = function(userId) {
$.ajax({
type: 'DELETE',
url: '/api/admin/users/' + userId,
success: function (response) {
alert('사용자 삭제가 완료되었습니다.');
window.location.href = '/admin/users';
},
error: function (error) {
alert('사용자 삭제에 실패하였습니다.');
window.location.href = '/admin/users';
}
});
};
});
/admin/users로 관리자가 접근하면 user 목록을 뽑아서 model에 넣어서 admin.html 페이지를 반환해준다.
@Controller
@RequiredArgsConstructor
public class AdminMixedController {
private final UserRepository userRepository;
@GetMapping("/admin/users")
public String adminView(Model model) {
List<User> users = userRepository.findAll();
List<UserResponseDto> userResponseDtos = users.stream().
map(UserResponseDto::new).collect(Collectors.toList());
model.addAttribute("users", userResponseDtos);
return "admin";
}
}
관리자가 유저를 삭제를 위한 api
@Controller
@RequestMapping("/api/admin")
@RequiredArgsConstructor
public class AdminController {
private final AdminService adminService;
@DeleteMapping("/users/{userId}")
public ResponseEntity<MessageDto> deleteUser(@PathVariable Long userId,
@AuthenticationPrincipal UserDetailsImpl userDetails) {
return ResponseEntity.ok(adminService.deleteUser(userId, userDetails.getUser()));
}
}
이 페이지는 관리자가 책나눔 이벤트를 관리하기 위한 페이지로 이벤트에 참여할 책들을 선택하고 정보를 업데이트 할 수 있다.
관리자는 이 페이지에서 새로운 이벤트를 생성하거나 기존의 이벤트를 수정/삭제할 수 있다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!-- 헤드 레이아웃 적용 -->
<head th:insert="~{layout :: head}"></head>
<head>
<!-- 책 나눔 이벤트 관리자 페이지-->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="/js/admin/donation.js"></script>
</head>
<body>
<!-- 네비게이션 메뉴 레이아웃 적용 -->
<div th:replace="~{layout :: nav}"></div>
<!-- 헤더 레이아웃 적용 -->
<div th:replace="~{layout :: header}"></div>
<!-- 컨텐츠(이 부분이 변경 됩니다.) -->
<div class="index-contents">
<div class="container">
<div class="container-container1">
<span class="container-text"><span>Book Donation Event List - ADMIN ONLY</span></span>
<table class="table table-striped">
<thead>
<tr>
<th>Event ID</th>
<th>Event Start Time</th>
<th>Event End Time</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<th:block th:each="event : ${events}">
<!-- 이벤트 정보 행 -->
<tr>
<td><input class="form-control" type="text" th:value="${event.donationId}" name="donationId"
readonly/></td>
<td><input class="form-control" type="text" th:value="${event.createdAt}" name="createdAt"/>
</td>
<td><input class="form-control" type="text" th:value="${event.closedAt}" name="closedAt"/></td>
<td>
<button type="button" class="btn btn-outline-success"
th:onclick="'location.href=\'/admin/donation/bookSetting/' + ${event.donationId} + '\''">
Book Setting Event
</button>
<button type="button" class="btn btn-outline-warning"
th:onclick="'updateDonationEvent(\'' + ${event.donationId} + '\')'">Update Event
</button>
<button type="button" class="btn btn-outline-danger"
th:onclick="'deleteDonationEvent(\'' + ${event.donationId} + '\')'">Delete Event
</button>
<button type="button" class="btn btn-outline-danger"
th:onclick="'endDonationEvent(\'' + ${event.donationId} + '\')'">End Event
</button>
</td>
</tr>
<!-- 책 정보 행 -->
<tr>
<td colspan="5">
<table class="table table-bordered">
<thead>
<tr>
<th>Book ID</th>
<th>Book Name</th>
<th>Author</th>
<th>Publisher</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr th:each="book : ${event.bookResponseDtos}">
<td th:text="${book.bookId}"></td>
<td th:text="${book.bookName}"></td>
<td th:text="${book.bookAuthor}"></td>
<td th:text="${book.bookPublish}"></td>
<td th:text="${book.bookStatus}"></td>
<td>
<button type="button" class="btn btn-outline-dark"
th:onclick="'bookApplyCancle(\'' + ${event.donationId} + '\', \'' + ${book.bookId} + '\')'">
Book Apply Cancel
</button>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</th:block>
<tr>
<td><input class="form-control" type="text" th:value="'*'" name="donationId" readonly/></td>
<td><input class="form-control" type="text" th:value="' '" name="createdAt"/></td>
<td><input class="form-control" type="text" th:value="' '" name="closedAt"/></td>
<td>
<button type="button" class="btn btn-outline-dark" th:onclick="createDonationEvent()">Create
Event
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 푸터 레이아웃 적용 -->
<div th:replace="~{layout :: footer}"></div>
</body>
</html>
$(document).ready(function() {
window.createDonationEvent = function() {
var data = {
createdAt: $('input[name="createdAt"]').last().val(),
closedAt: $('input[name="closedAt"]').last().val()
};
$.ajax({
type: 'POST',
url: '/api/admin/donation',
data: JSON.stringify(data),
contentType: 'application/json;charset=UTF-8',
dataType: 'json',
success: function(response) {
alert('이벤트 생성 성공!');
window.location.href = '/admin/donation';
},
error: function(xhr, status, error) {
alert('이벤트 생성 실패!');
window.location.href = '/admin/donation';
}
});
};
window.updateDonationEvent = function(donationId) {
var row = $('input[name="donationId"][value="' + donationId + '"]').closest('tr');
var data = {
createdAt: row.find('input[name="createdAt"]').val(),
closedAt: row.find('input[name="closedAt"]').val()
};
$.ajax({
type: 'PUT',
url: '/api/admin/donation/' + donationId,
data: JSON.stringify(data),
contentType: 'application/json;charset=UTF-8',
dataType: 'json',
success: function(response) {
alert('이벤트 업데이트 성공!');
window.location.href = '/admin/donation';
},
error: function(xhr, status, error) {
alert('이벤트 업데이트 실패!');
window.location.href = '/admin/donation';
}
});
};
window.deleteDonationEvent = function(donationId) {
if(confirm('정말로 이 이벤트를 삭제하시겠습니까?')) {
$.ajax({
type: 'DELETE',
url: '/api/admin/donation/' + donationId,
contentType: 'application/json;charset=UTF-8',
dataType: 'json',
success: function(response) {
alert('이벤트 삭제 성공!');
window.location.href = '/admin/donation';
},
error: function(xhr, status, error) {
alert('이벤트 삭제 실패!');
window.location.href = '/admin/donation';
}
});
}
};
window.endDonationEvent = function(donationId) {
if(confirm('정말로 이 이벤트를 삭제하시겠습니까?')) {
$.ajax({
type: 'DELETE',
url: '/api/admin/donation/end/' + donationId,
contentType: 'application/json;charset=UTF-8',
dataType: 'json',
success: function(response) {
alert('이벤트 종료 성공!');
window.location.href = '/admin/donation';
},
error: function(xhr, status, error) {
alert('이벤트 종료 실패!');
window.location.href = '/admin/donation';
}
});
}
};
window.setDonationEvent = function() {
var selectedBooks = [];
$('.book-checkbox:checked').each(function() {
selectedBooks.push($(this).val());
});
if (selectedBooks.length === 0) {
alert('설정할 책을 선택해주세요.');
return;
}
var data = {
donationId: $('#donationId').val(),
bookIds: selectedBooks.map(Number)
};
$.ajax({
type: 'PUT',
url: '/api/admin/donation/setting',
data: JSON.stringify(data),
contentType: 'application/json;charset=UTF-8',
dataType: 'json',
success: function(response) {
alert('이벤트 설정 성공!');
window.location.href = '/admin/donation';
},
error: function(xhr, status, error) {
alert('이벤트 설정 실패!');
}
});
};
window.bookApplyCancle = function(donationId, bookId) {
const requestData = {
donationId: donationId,
bookId: bookId
};
$.ajax({
type: 'PUT',
url: '/api/admin/donation/settingCancel',
contentType: 'application/json',
data: JSON.stringify(requestData),
success: function (response) {
alert('책 취소 설정이 완료되었습니다.');
window.location.reload(); // 현재 페이지 새로고침
},
error: function (error) {
alert('책 취소 설정에 실패하였습니다.');
}
});
};
});
이벤트 목록과 해당 이벤트에 대상인 책들을 Model에 저장해서 donation 페이지로 같이 보낸다.
@Controller
@RequestMapping("/admin/donation")
@RequiredArgsConstructor
public class DonationViewController {
private final BookDonationEventService bookDonationEventService;
private final BookApplyDonationService bookApplyDonationService;
@GetMapping
public String donation(Model model) {
List<BookDonationEventResponseDto> bookDonationEventResponseDtos = bookDonationEventService.getDonationEvent();
model.addAttribute("events", bookDonationEventResponseDtos);
return "/admin/donation";
}
@GetMapping("/bookSetting/{donationId}")
public String bookSetting(@PathVariable Long donationId, Model model) {
List<BookResponseDto> bookResponseDtos = bookApplyDonationService.getDonationBooks(BookStatusEnum.POSSIBLE);
model.addAttribute("books", bookResponseDtos);
model.addAttribute("donationId", donationId);
return "/admin/bookSetting";
}
}
해당 페이지의 이벤트 관리 기능을 위한 api
package com.example.team258.controller.serviceController;
import com.example.team258.dto.*;
import com.example.team258.service.BookDonationEventService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/admin/donation")
@RequiredArgsConstructor
public class BookDonationEventController {
private final BookDonationEventService bookDonationEventService;
@PostMapping
public ResponseEntity<MessageDto> createDonationEvent(@RequestBody BookDonationEventRequestDto bookDonationEventRequestDto){
return bookDonationEventService.createDonationEvent(bookDonationEventRequestDto);
}
@PutMapping("/{donationId}")
public ResponseEntity<MessageDto> updateDonationEvent(@PathVariable Long donationId, @RequestBody BookDonationEventRequestDto bookDonationEventRequestDto){
return bookDonationEventService.updateDonationEvent(donationId,bookDonationEventRequestDto);
}
@DeleteMapping("/{donationId}")
public ResponseEntity<MessageDto> deleteDonationEvent(@PathVariable Long donationId){
return bookDonationEventService.deleteDonationEvent(donationId);
}
@GetMapping
public ResponseEntity<List<BookDonationEventResponseDto>> getDonationEvent(){
return ResponseEntity.ok().body(bookDonationEventService.getDonationEvent());
}
@PutMapping("/setting")
public ResponseEntity<MessageDto> settingDonationEvent(@RequestBody BookDonationSettingRequestDto bookDonationSettingRequestDto){
return ResponseEntity.ok().body(bookDonationEventService.settingDonationEvent(bookDonationSettingRequestDto));
}
@PutMapping("/settingCancel")
public ResponseEntity<MessageDto> settingCancelDonationEvent(@RequestBody BookDonationSettingCancelRequestDto bookDonationSettingCancelRequestDto){
return ResponseEntity.ok().body(bookDonationEventService.settingCancelDonationEvent(bookDonationSettingCancelRequestDto));
}
@DeleteMapping("/end/{donationId}")
public ResponseEntity<MessageDto> endDonationEvent(@PathVariable Long donationId){
return ResponseEntity.ok().body(bookDonationEventService.endDonationEvent(donationId));
}
}
@Controller
@RequestMapping("/admin/donation")
@RequiredArgsConstructor
public class DonationViewController {
private final BookDonationEventService bookDonationEventService;
private final BookApplyDonationService bookApplyDonationService;
@GetMapping("/bookSetting/{donationId}")
public String bookSetting(@PathVariable Long donationId, Model model) {
List<BookResponseDto> bookResponseDtos = bookApplyDonationService.getDonationBooks(BookStatusEnum.POSSIBLE);
model.addAttribute("books", bookResponseDtos);
model.addAttribute("donationId", donationId);
return "/admin/bookSetting";
}
}
package com.example.team258.controller.serviceController;
import com.example.team258.dto.*;
import com.example.team258.service.BookDonationEventService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/admin/donation")
@RequiredArgsConstructor
public class BookDonationEventController {
private final BookDonationEventService bookDonationEventService;
@PutMapping("/setting")
public ResponseEntity<MessageDto> settingDonationEvent(@RequestBody BookDonationSettingRequestDto bookDonationSettingRequestDto){
return ResponseEntity.ok().body(bookDonationEventService.settingDonationEvent(bookDonationSettingRequestDto));
}
}
나눔 이벤트에 사용할 책 등록 페이지
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!-- 헤드 레이아웃 적용 -->
<head th:insert="~{layout :: head}"></head>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="/js/admin/donation.js"></script>
<body>
<!-- 네비게이션 메뉴 레이아웃 적용 -->
<div th:replace="~{layout :: nav}"></div>
<!-- 헤더 레이아웃 적용 -->
<div th:replace="~{layout :: header}"></div>
<!-- 컨텐츠(이 부분이 변경 됩니다.) -->
<div class="index-contents">
<div class="container">
<div class="container-container1">
<span class="container-text"><span>Book Donation Event List - ADMIN ONLY</span></span>
<table class="table table-striped">
<thead>
<tr>
<th>Book Event Setting</th>
<th>Book ID</th>
<th>Book Name</th>
<th>Book Author</th>
<th>Book Publish</th>
<th>Book Status</th>
</tr>
</thead>
<input type="hidden" id="donationId" th:value="${donationId}"/>
<tbody>
<tr th:each="book : ${books}">
<td><input type="checkbox" class="book-checkbox" th:value="${book.bookId}"/></td>
<td th:text="${book.bookId}"></td>
<td th:text="${book.bookName}"></td>
<td th:text="${book.bookAuthor}"></td>
<td th:text="${book.bookPublish}"></td>
<td th:text="${book.bookStatus}"></td>
</tr>
</tbody>
</table>
<button type="button" class="btn btn-primary" onclick="setDonationEvent()">이벤트 설정</button>
</div>
</div>
</div>
<!-- 푸터 레이아웃 적용 -->
<div th:replace="~{layout :: footer}"></div>
</body>
</html>
package com.example.team258.controller.viewController.user;
import java.util.List;
@Controller
@RequestMapping("/users/bookDonationEvent")
@RequiredArgsConstructor
public class BookDonationEventViewController {
private final BookDonationEventService bookDonationEventService;
private final BookDonationEventRepository bookDonationEventRepository;
@GetMapping
public String bookApplyDonation(Model model) {
List<BookDonationEventResponseDto> bookResponseDtos = bookDonationEventService.getDonationEvent();
model.addAttribute("events", bookResponseDtos);
return "/users/bookDonationEvent";
}
}
이 페이지는 일반 유저가 이벤트에 접근하기 위한 페이지이다.
여기서 유저는 각 이벤트의 세부 정보를 확인 할 수 있다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!-- 헤드 레이아웃 적용 -->
<head th:replace="~{layout :: head}"></head>
<body>
<!-- 네비게이션 메뉴 레이아웃 적용 -->
<div th:replace="~{layout :: nav}"></div>
<!-- 헤더 레이아웃 적용 -->
<div th:replace="~{layout :: header}"></div>
<!-- 컨텐츠(이 부분이 변경 됩니다.) -->
<div class="index-contents">
<div class="container">
<div class="container-container1">
<span class="container-text"><span>Book Donation Event List</span></span>
<table class="table table-striped">
<thead>
<tr>
<th>Event ID</th>
<th>Event Start Time</th>
<th>Event End Time</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr th:each="event : ${events}">
<td><label>
<input class="form-control" type="text" th:value="${event.donationId}" name="donationId" readonly/>
</label></td>
<td><input class="form-control" type="text" th:value="${event.createdAt}" name="createdAt"/></td>
<td><input class="form-control" type="text" th:value="${event.closedAt}" name="closedAt"/></td>
<td>
<button type="button" class="btn btn-outline-success" th:onclick="'location.href=\'/users/bookDonationEvent/' + ${event.donationId} + '\''">Event Page</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 푸터 레이아웃 적용 -->
<div th:replace="~{layout :: footer}"></div>
</body>
</html>
유저가 이벤트에 참여하는 페이지이다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!-- 헤드 레이아웃 적용 -->
<head th:insert="~{layout :: head}"></head>
<head>
<!-- 카테고리 관리자 페이지-->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="/js/users/bookApplyDonation.js"></script>
</head>
<body>
<!-- 네비게이션 메뉴 레이아웃 적용 -->
<div th:replace="~{layout :: nav}"></div>
<!-- 헤더 레이아웃 적용 -->
<div th:insert="~{layout :: header}"></div>
<!-- 컨텐츠(이 부분이 변경 됩니다.) -->
<div class="index-contents">
<div class="container">
<div class="container-container1">
<input type="hidden" id="donationId" th:value="${bookDonationEvent.donationId}" />
<!-- 중앙 강조 이벤트 시간 표시 -->
<div class="text-center my-4">
<h1 style="font-size: 2.5em; color: #007BFF;">이벤트 시작 시간:</h1>
<h2 style="font-size: 2em; color: #000;"><span th:text="${bookDonationEvent.createdAt}"></span></h2>
<h1 style="font-size: 2.5em; color: #DC3545;">이벤트 종료 시간:</h1>
<h2 style="font-size: 2em; color: #000;"><span th:text="${bookDonationEvent.closedAt}"></span></h2>
</div>
<hr> <!-- 구분선 -->
<span class="container-text"><span>Book Apply Donation Lists</span></span>
<table class="table table-striped">
<thead>
<tr>
<th>Book Id</th>
<th>Book Name</th>
<th>Author</th>
<th>Publish Date</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr th:each="book : ${books}">
<td><input class="form-control" type="text" th:value="${book.bookId}" name="bookId" readonly/></td>
<td><input class="form-control" type="text" th:value="${book.bookName}" name="bookName" readonly/></td>
<td><input class="form-control" type="text" th:value="${book.bookAuthor}" name="bookAuthor" readonly/></td>
<td><input class="form-control" type="text" th:value="${book.bookPublish}" name="bookPublish" readonly/></td>
<td><input class="form-control" type="text" th:value="${book.bookStatus}" name="bookStatus" readonly/></td>
<td><button type="button" class="btn btn-outline-danger" th:onclick="'bookDonationApply(\'' + ${book.bookId} + '\')'">APPLY</button></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 푸터 레이아웃 적용 -->
<div th:replace="~{layout :: footer}"></div>
</body>
</html>
$(document).ready(function() {
window.bookDonationApply = function(bookId) {
var applyDate = new Date().toISOString(); // 현재 날짜 및 시간을 ISO 형식으로 가져옵니다.
var donationId = $('#donationId').val(); // 숨겨진 input 필드에서 donationId 값을 가져옵니다.
var data = {
donationId: donationId,
bookId: bookId,
applyDate: applyDate
};
$.ajax({
type: 'POST',
url: '/api/user/bookApplyDonation',
data: JSON.stringify(data),
contentType: 'application/json;charset=UTF-8',
dataType: 'json',
success: function(response) {
alert('나눔 신청 성공!');
location.reload(); // 페이지를 다시 로드하여 최신 정보를 표시합니다.
},
error: function(xhr, status, error) {
alert('나눔 신청 실패!');
}
});
};
});
유저가 이벤트 참여 신청을 한 후 다시 신청을 취소하고자 할 때 사용하는 페이지이다.
유저는 자신이 신청한 책을 확인하고 그 중에서 신청을 취소하고자 하는 신청을 취소할 수 있습니다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:insert="~{layout :: head}"></head>
<head>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="/js/users/bookApplyCancel.js"></script>
</head>
<body>
<div th:replace="~{layout :: nav}"></div>
<div th:replace="~{layout :: header}"></div>
<div class="index-contents">
<div class="container">
<div class="container-container1">
<span class="container-text"><span>나눔 신청한 책 목록</span></span>
<table class="table table-striped">
<thead>
<tr>
<th>책 ID</th>
<th>책 이름</th>
<th>저자</th>
<th>출판사</th>
<th>상태</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr th:each="book, stat : ${userBookApplyCancelPageResponseDto.bookResponseDto}">
<td th:text="${book.bookId}"></td>
<td th:text="${book.bookName}"></td>
<td th:text="${book.bookAuthor}"></td>
<td th:text="${book.bookPublish}"></td>
<td th:text="${book.bookStatus}"></td>
<td>
<button type="button" class="btn btn-outline-danger"
th:onclick="'deleteBookApplyDonation(\'' + ${userBookApplyCancelPageResponseDto.BookApplyId[stat.index]} + '\')'">나눔 신청 취소</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div th:replace="~{layout :: footer}"></div>
</body>
</html>
$(document).ready(function () {
window.deleteBookApplyDonation = function(applyId) {
$.ajax({
type: 'DELETE',
url: '/api/user/bookApplyDonation/' + applyId,
success: function (response) {
alert('나눔 신청 취소가 완료되었습니다.');
window.location.reload(); // 페이지 새로고침
},
error: function (error) {
alert('나눔 신청 취소에 실패하였습니다.');
window.location.reload(); // 페이지 새로고침
}
});
};
});