generated from pricelees/issue-pr-template
[#22] 프론트엔드 React 전환 및 인증 API 수정 #23
@ -32,7 +32,6 @@ repositories {
|
|||||||
dependencies {
|
dependencies {
|
||||||
// Spring
|
// Spring
|
||||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
|
|
||||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-validation")
|
implementation("org.springframework.boot:spring-boot-starter-validation")
|
||||||
|
|
||||||
@ -71,8 +70,8 @@ tasks.withType<Test> {
|
|||||||
tasks.withType<KotlinCompile> {
|
tasks.withType<KotlinCompile> {
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
freeCompilerArgs.addAll(
|
freeCompilerArgs.addAll(
|
||||||
"-Xjsr305=strict",
|
"-Xjsr305=strict",
|
||||||
"-Xannotation-default-target=param-property"
|
"-Xannotation-default-target=param-property"
|
||||||
)
|
)
|
||||||
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
|
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,48 +0,0 @@
|
|||||||
package roomescape.view
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Controller
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
|
||||||
import roomescape.auth.web.support.Admin
|
|
||||||
import roomescape.auth.web.support.LoginRequired
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
class AuthPageController {
|
|
||||||
|
|
||||||
@GetMapping("/login")
|
|
||||||
fun showLoginPage(): String = "login"
|
|
||||||
}
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
@RequestMapping("/admin")
|
|
||||||
class AdminPageController {
|
|
||||||
|
|
||||||
@Admin
|
|
||||||
@GetMapping
|
|
||||||
fun showIndexPage() = "admin/index"
|
|
||||||
|
|
||||||
@Admin
|
|
||||||
@GetMapping("/{page}")
|
|
||||||
fun showAdminSubPage(@PathVariable page: String) = when (page) {
|
|
||||||
"reservation" -> "admin/reservation-new"
|
|
||||||
"time" -> "admin/time"
|
|
||||||
"theme" -> "admin/theme"
|
|
||||||
"waiting" -> "admin/waiting"
|
|
||||||
else -> "admin/index"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
class ClientPageController {
|
|
||||||
@GetMapping("/")
|
|
||||||
fun showPopularThemePage(): String = "index"
|
|
||||||
|
|
||||||
@LoginRequired
|
|
||||||
@GetMapping("/reservation")
|
|
||||||
fun showReservationPage(): String = "reservation"
|
|
||||||
|
|
||||||
@LoginRequired
|
|
||||||
@GetMapping("/reservation-mine")
|
|
||||||
fun showReservationMinePage(): String = "reservation-mine"
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
.disabled {
|
|
||||||
pointer-events: none;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
#theme-slots .theme-slot.active, #time-slots .time-slot.active {
|
|
||||||
background-color: #0a3711 !important;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
#time-slots .time-slot.disabled {
|
|
||||||
background-color: #cccccc;
|
|
||||||
color: #666666;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
.profile-image {
|
|
||||||
height: 30px;
|
|
||||||
width: 30px;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-right: 5px; /* 이름과의 간격 조정 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item .dropdown-toggle::after {
|
|
||||||
display: none; /* 드롭다운 화살표 제거 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item {
|
|
||||||
margin-right: 10px; /* 네비게이션 간격 조정 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-container {
|
|
||||||
width: 70%;
|
|
||||||
margin: 50px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-container-title {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
margin: 10px 0;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Solid 버튼 */
|
|
||||||
.btn-custom {
|
|
||||||
background-color: #0a3711; /* 버튼 기본 배경색 */
|
|
||||||
color: white; /* 버튼 텍스트 색상 */
|
|
||||||
border: 1px solid #0a3711; /* 테두리 색상 일치 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-custom:hover {
|
|
||||||
background-color: #083d0f; /* 호버 상태에서의 배경색 */
|
|
||||||
color: white; /* 호버 상태에서의 텍스트 색상 */
|
|
||||||
border: 1px solid #083d0f; /* 호버 상태에서의 테두리 색상 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Outline 버튼 */
|
|
||||||
.btn-outline-custom {
|
|
||||||
background-color: transparent; /* 버튼 기본 배경색 투명 */
|
|
||||||
color: #0a3711; /* 버튼 텍스트 색상 */
|
|
||||||
border: 1px solid #0a3711; /* 테두리 색상 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-outline-custom:hover {
|
|
||||||
background-color: #0a3711; /* 호버 상태에서의 배경색 */
|
|
||||||
color: white; /* 호버 상태에서의 텍스트 색상 */
|
|
||||||
border: 1px solid #0a3711; /* 호버 상태에서의 테두리 색상 유지 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.cursor-pointer {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
@ -1,132 +0,0 @@
|
|||||||
.w-100 {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-100 {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
padding: 24px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.max-w-540 {
|
|
||||||
max-width: 540px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-wrapper {
|
|
||||||
padding: 0 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
padding: 11px 22px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
background-color: #f2f4f6;
|
|
||||||
color: #4e5968;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 17px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn.primary {
|
|
||||||
background-color: #3282f6;
|
|
||||||
color: #f9fcff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-column {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.justify-center {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.justify-between {
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align-center {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-loading {
|
|
||||||
margin-top: 72px;
|
|
||||||
height: 400px;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.confirm-success {
|
|
||||||
display: none;
|
|
||||||
margin-top: 72px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-group {
|
|
||||||
margin-top: 32px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
margin-top: 32px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
color: #191f28;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
margin-top: 8px;
|
|
||||||
color: #4e5968;
|
|
||||||
font-size: 17px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.response-section {
|
|
||||||
margin-top: 60px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.response-section .response-label {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333d48;
|
|
||||||
font-size: 17px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.response-section .response-text {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #4e5968;
|
|
||||||
font-size: 17px;
|
|
||||||
padding-left: 16px;
|
|
||||||
word-break: break-word;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-grey {
|
|
||||||
color: #b0b8c1;
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB |
@ -1,45 +0,0 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
requestRead(`/themes/most-reserved-last-week?count=10`) // 인기 테마 목록 조회 API endpoint
|
|
||||||
.then(render)
|
|
||||||
.catch(error => console.error('Error fetching times:', error));
|
|
||||||
});
|
|
||||||
|
|
||||||
function formatDate(dateString) {
|
|
||||||
let date = new Date(dateString);
|
|
||||||
let year = date.getFullYear();
|
|
||||||
let month = (date.getMonth() + 1).toString().padStart(2, '0'); // '04'
|
|
||||||
let day = date.getDate().toString().padStart(2, '0'); // '28'
|
|
||||||
|
|
||||||
return `${year}-${month}-${day}`; // '2024-04-28'
|
|
||||||
}
|
|
||||||
|
|
||||||
function render(data) {
|
|
||||||
const container = document.getElementById('theme-ranking');
|
|
||||||
data.data.themes.forEach(theme => {
|
|
||||||
const name = theme.name;
|
|
||||||
const thumbnail = theme.thumbnail;
|
|
||||||
const description = theme.description;
|
|
||||||
|
|
||||||
const htmlContent = `
|
|
||||||
<img class="mr-3 img-thumbnail" src="${thumbnail}" alt="${name}">
|
|
||||||
<div class="media-body">
|
|
||||||
<h5 class="mt-0 mb-1">${name}</h5>
|
|
||||||
${description}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const div = document.createElement('li');
|
|
||||||
div.className = 'media my-4';
|
|
||||||
div.innerHTML = htmlContent;
|
|
||||||
|
|
||||||
container.appendChild(div);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestRead(endpoint) {
|
|
||||||
return fetch(endpoint)
|
|
||||||
.then(response => {
|
|
||||||
if (response.status === 200) return response.json();
|
|
||||||
throw new Error('Read failed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
fetch('/reservations-mine') // 내 예약 목록 조회 API 호출
|
|
||||||
.then(response => {
|
|
||||||
if (response.status === 200) return response.json();
|
|
||||||
throw new Error('Read failed');
|
|
||||||
})
|
|
||||||
.then(render)
|
|
||||||
.catch(error => console.error('Error fetching reservations:', error));
|
|
||||||
});
|
|
||||||
|
|
||||||
function render(data) {
|
|
||||||
const tableBody = document.getElementById('table-body');
|
|
||||||
tableBody.innerHTML = '';
|
|
||||||
data.data.reservations.forEach(item => {
|
|
||||||
const row = tableBody.insertRow();
|
|
||||||
|
|
||||||
const theme = item.themeName;
|
|
||||||
const date = item.date;
|
|
||||||
const time = item.time;
|
|
||||||
const status = item.status.includes('CONFIRMED') ? (item.status === 'CONFIRMED' ? '예약' : '예약 - 결제 필요') : item.rank + '번째 예약 대기';
|
|
||||||
|
|
||||||
row.insertCell(0).textContent = theme;
|
|
||||||
row.insertCell(1).textContent = date;
|
|
||||||
row.insertCell(2).textContent = time;
|
|
||||||
row.insertCell(3).textContent = status;
|
|
||||||
|
|
||||||
if (status.includes('대기')) { // 예약 대기 상태일 때 예약 대기 취소 버튼 추가하는 코드, 상태 값은 변경 가능
|
|
||||||
const cancelCell = row.insertCell(4);
|
|
||||||
const cancelButton = document.createElement('button');
|
|
||||||
cancelButton.textContent = '취소';
|
|
||||||
cancelButton.className = 'btn btn-danger';
|
|
||||||
cancelButton.onclick = function () {
|
|
||||||
requestDeleteWaiting(item.id).then(() => window.location.reload());
|
|
||||||
};
|
|
||||||
cancelCell.appendChild(cancelButton);
|
|
||||||
} else { // 예약 완료 상태일 때
|
|
||||||
/*
|
|
||||||
TODO: [미션4 - 2단계] 내 예약 목록 조회 시,
|
|
||||||
예약 완료 상태일 때 결제 정보를 함께 보여주기
|
|
||||||
결제 정보 필드명은 자신의 response 에 맞게 변경하기
|
|
||||||
*/
|
|
||||||
row.insertCell(4).textContent = '';
|
|
||||||
row.insertCell(5).textContent = item.paymentKey;
|
|
||||||
row.insertCell(6).textContent = item.amount;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestDeleteWaiting(id) {
|
|
||||||
const endpoint = '/reservations/waiting/' + id;
|
|
||||||
return fetch(endpoint, {
|
|
||||||
method: 'DELETE'
|
|
||||||
}).then(response => {
|
|
||||||
if (response.status === 204) return;
|
|
||||||
throw new Error('Delete failed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,194 +0,0 @@
|
|||||||
let isEditing = false;
|
|
||||||
const RESERVATION_API_ENDPOINT = '/reservations';
|
|
||||||
const TIME_API_ENDPOINT = '/times';
|
|
||||||
const THEME_API_ENDPOINT = '/themes';
|
|
||||||
const timesOptions = [];
|
|
||||||
const themesOptions = [];
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
document.getElementById('add-button').addEventListener('click', addInputRow);
|
|
||||||
|
|
||||||
requestRead(RESERVATION_API_ENDPOINT)
|
|
||||||
.then(render)
|
|
||||||
.catch(error => console.error('Error fetching reservations:', error));
|
|
||||||
|
|
||||||
fetchTimes();
|
|
||||||
fetchThemes();
|
|
||||||
});
|
|
||||||
|
|
||||||
function render(data) {
|
|
||||||
const tableBody = document.getElementById('table-body');
|
|
||||||
tableBody.innerHTML = '';
|
|
||||||
|
|
||||||
data.data.reservations.forEach(item => {
|
|
||||||
const row = tableBody.insertRow();
|
|
||||||
|
|
||||||
row.insertCell(0).textContent = item.id; // 예약 id
|
|
||||||
row.insertCell(1).textContent = item.name; // 예약자명
|
|
||||||
row.insertCell(2).textContent = item.theme.name; // 테마명
|
|
||||||
row.insertCell(3).textContent = item.date; // 예약 날짜
|
|
||||||
row.insertCell(4).textContent = item.time.startAt; // 시작 시간
|
|
||||||
|
|
||||||
const actionCell = row.insertCell(row.cells.length);
|
|
||||||
actionCell.appendChild(createActionButton('삭제', 'btn-danger', deleteRow));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchTimes() {
|
|
||||||
requestRead(TIME_API_ENDPOINT)
|
|
||||||
.then(data => {
|
|
||||||
timesOptions.push(...data.data.times);
|
|
||||||
})
|
|
||||||
.catch(error => console.error('Error fetching time:', error));
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchThemes() {
|
|
||||||
requestRead(THEME_API_ENDPOINT)
|
|
||||||
.then(data => {
|
|
||||||
themesOptions.push(...data.data.themes);
|
|
||||||
})
|
|
||||||
.catch(error => console.error('Error fetching theme:', error));
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSelect(options, defaultText, selectId, textProperty) {
|
|
||||||
const select = document.createElement('select');
|
|
||||||
select.className = 'form-control';
|
|
||||||
select.id = selectId;
|
|
||||||
|
|
||||||
// 기본 옵션 추가
|
|
||||||
const defaultOption = document.createElement('option');
|
|
||||||
defaultOption.textContent = defaultText;
|
|
||||||
select.appendChild(defaultOption);
|
|
||||||
|
|
||||||
// 넘겨받은 옵션을 바탕으로 드롭다운 메뉴 아이템 생성
|
|
||||||
options.forEach(optionData => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = optionData.id;
|
|
||||||
option.textContent = optionData[textProperty]; // 동적 속성 접근
|
|
||||||
select.appendChild(option);
|
|
||||||
});
|
|
||||||
|
|
||||||
return select;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createActionButton(label, className, eventListener) {
|
|
||||||
const button = document.createElement('button');
|
|
||||||
button.textContent = label;
|
|
||||||
button.classList.add('btn', className, 'mr-2');
|
|
||||||
button.addEventListener('click', eventListener);
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addInputRow() {
|
|
||||||
if (isEditing) return; // 이미 편집 중인 경우 추가하지 않음
|
|
||||||
|
|
||||||
const tableBody = document.getElementById('table-body');
|
|
||||||
const row = tableBody.insertRow();
|
|
||||||
isEditing = true;
|
|
||||||
|
|
||||||
const nameInput = createInput('text');
|
|
||||||
const dateInput = createInput('date');
|
|
||||||
const timeDropdown = createSelect(timesOptions, "시간 선택", 'time-select', 'startAt');
|
|
||||||
const themeDropdown = createSelect(themesOptions, "테마 선택", 'theme-select', 'name');
|
|
||||||
|
|
||||||
const cellFieldsToCreate = ['', nameInput, themeDropdown, dateInput, timeDropdown];
|
|
||||||
|
|
||||||
cellFieldsToCreate.forEach((field, index) => {
|
|
||||||
const cell = row.insertCell(index);
|
|
||||||
if (typeof field === 'string') {
|
|
||||||
cell.textContent = field;
|
|
||||||
} else {
|
|
||||||
cell.appendChild(field);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionCell = row.insertCell(row.cells.length);
|
|
||||||
actionCell.appendChild(createActionButton('확인', 'btn-custom', saveRow));
|
|
||||||
actionCell.appendChild(createActionButton('취소', 'btn-secondary', () => {
|
|
||||||
row.remove();
|
|
||||||
isEditing = false;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function createInput(type) {
|
|
||||||
const input = document.createElement('input');
|
|
||||||
input.type = type;
|
|
||||||
input.className = 'form-control';
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createActionButton(label, className, eventListener) {
|
|
||||||
const button = document.createElement('button');
|
|
||||||
button.textContent = label;
|
|
||||||
button.classList.add('btn', className, 'mr-2');
|
|
||||||
button.addEventListener('click', eventListener);
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveRow(event) {
|
|
||||||
// 이벤트 전파를 막는다
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
const row = event.target.parentNode.parentNode;
|
|
||||||
const nameInput = row.querySelector('input[type="text"]');
|
|
||||||
const themeSelect = row.querySelector('#theme-select');
|
|
||||||
const dateInput = row.querySelector('input[type="date"]');
|
|
||||||
const timeSelect = row.querySelector('#time-select');
|
|
||||||
|
|
||||||
const reservation = {
|
|
||||||
name: nameInput.value,
|
|
||||||
themeId: themeSelect.value,
|
|
||||||
date: dateInput.value,
|
|
||||||
timeId: timeSelect.value
|
|
||||||
};
|
|
||||||
|
|
||||||
requestCreate(reservation)
|
|
||||||
.then(() => {
|
|
||||||
location.reload();
|
|
||||||
})
|
|
||||||
.catch(error => console.error('Error:', error));
|
|
||||||
|
|
||||||
isEditing = false; // isEditing 값을 false로 설정
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteRow(event) {
|
|
||||||
const row = event.target.closest('tr');
|
|
||||||
const reservationId = row.cells[0].textContent;
|
|
||||||
|
|
||||||
requestDelete(reservationId)
|
|
||||||
.then(() => row.remove())
|
|
||||||
.catch(error => console.error('Error:', error));
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestCreate(reservation) {
|
|
||||||
const requestOptions = {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify(reservation)
|
|
||||||
};
|
|
||||||
|
|
||||||
return fetch(RESERVATION_API_ENDPOINT, requestOptions)
|
|
||||||
.then(response => {
|
|
||||||
if (response.status === 201) return response.json();
|
|
||||||
throw new Error('Create failed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestDelete(id) {
|
|
||||||
const requestOptions = {
|
|
||||||
method: 'DELETE',
|
|
||||||
};
|
|
||||||
|
|
||||||
return fetch(`${RESERVATION_API_ENDPOINT}/${id}`, requestOptions)
|
|
||||||
.then(response => {
|
|
||||||
if (response.status !== 204) throw new Error('Delete failed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestRead(endpoint) {
|
|
||||||
return fetch(endpoint)
|
|
||||||
.then(response => {
|
|
||||||
if (response.status === 200) return response.json();
|
|
||||||
throw new Error('Read failed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,250 +0,0 @@
|
|||||||
let isEditing = false;
|
|
||||||
const RESERVATION_API_ENDPOINT = '/reservations';
|
|
||||||
const TIME_API_ENDPOINT = '/times';
|
|
||||||
const THEME_API_ENDPOINT = '/themes';
|
|
||||||
const MEMBER_API_ENDPOINT = '/members';
|
|
||||||
const timesOptions = [];
|
|
||||||
const themesOptions = [];
|
|
||||||
const membersOptions = [];
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
document.getElementById('add-button').addEventListener('click', addInputRow);
|
|
||||||
document.getElementById('filter-form').addEventListener('submit', applyFilter);
|
|
||||||
|
|
||||||
requestRead(RESERVATION_API_ENDPOINT)
|
|
||||||
.then(render)
|
|
||||||
.catch(error => console.error('Error fetching reservations:', error));
|
|
||||||
|
|
||||||
fetchTimes();
|
|
||||||
fetchThemes();
|
|
||||||
fetchMembers();
|
|
||||||
});
|
|
||||||
|
|
||||||
function render(data) {
|
|
||||||
const tableBody = document.getElementById('table-body');
|
|
||||||
tableBody.innerHTML = '';
|
|
||||||
|
|
||||||
data.data.reservations.forEach(item => {
|
|
||||||
const row = tableBody.insertRow();
|
|
||||||
const isPaid = item.status === 'CONFIRMED' ? '결제 완료' : '결제 대기';
|
|
||||||
|
|
||||||
row.insertCell(0).textContent = item.id; // 예약 id
|
|
||||||
row.insertCell(1).textContent = item.member.name; // 사용자 name
|
|
||||||
row.insertCell(2).textContent = item.theme.name; // 테마 name
|
|
||||||
row.insertCell(3).textContent = item.date; // date
|
|
||||||
row.insertCell(4).textContent = item.time.startAt; // 예약 시간 startAt
|
|
||||||
row.insertCell(5).textContent = isPaid; // 결제
|
|
||||||
|
|
||||||
const actionCell = row.insertCell(row.cells.length);
|
|
||||||
actionCell.appendChild(createActionButton('삭제', 'btn-danger', deleteRow));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchTimes() {
|
|
||||||
requestRead(TIME_API_ENDPOINT)
|
|
||||||
.then(data => {
|
|
||||||
timesOptions.push(...data.data.times);
|
|
||||||
})
|
|
||||||
.catch(error => console.error('Error fetching time:', error));
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchThemes() {
|
|
||||||
requestRead(THEME_API_ENDPOINT)
|
|
||||||
.then(data => {
|
|
||||||
themesOptions.push(...data.data.themes);
|
|
||||||
populateSelect('theme', themesOptions, 'name');
|
|
||||||
})
|
|
||||||
.catch(error => console.error('Error fetching theme:', error));
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchMembers() {
|
|
||||||
requestRead(MEMBER_API_ENDPOINT)
|
|
||||||
.then(data => {
|
|
||||||
membersOptions.push(...data.data.members);
|
|
||||||
populateSelect('member', membersOptions, 'name');
|
|
||||||
})
|
|
||||||
.catch(error => console.error('Error fetching member:', error));
|
|
||||||
}
|
|
||||||
|
|
||||||
function populateSelect(selectId, options, textProperty) {
|
|
||||||
const select = document.getElementById(selectId);
|
|
||||||
options.forEach(optionData => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = optionData.id;
|
|
||||||
option.textContent = optionData[textProperty];
|
|
||||||
select.appendChild(option);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSelect(options, defaultText, selectId, textProperty) {
|
|
||||||
const select = document.createElement('select');
|
|
||||||
select.className = 'form-control';
|
|
||||||
select.id = selectId;
|
|
||||||
|
|
||||||
// 기본 옵션 추가
|
|
||||||
const defaultOption = document.createElement('option');
|
|
||||||
defaultOption.textContent = defaultText;
|
|
||||||
select.appendChild(defaultOption);
|
|
||||||
|
|
||||||
// 넘겨받은 옵션을 바탕으로 드롭다운 메뉴 아이템 생성
|
|
||||||
options.forEach(optionData => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = optionData.id;
|
|
||||||
option.textContent = optionData[textProperty]; // 동적 속성 접근
|
|
||||||
select.appendChild(option);
|
|
||||||
});
|
|
||||||
|
|
||||||
return select;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createActionButton(label, className, eventListener) {
|
|
||||||
const button = document.createElement('button');
|
|
||||||
button.textContent = label;
|
|
||||||
button.classList.add('btn', className, 'mr-2');
|
|
||||||
button.addEventListener('click', eventListener);
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addInputRow() {
|
|
||||||
if (isEditing) return; // 이미 편집 중인 경우 추가하지 않음
|
|
||||||
|
|
||||||
const tableBody = document.getElementById('table-body');
|
|
||||||
const row = tableBody.insertRow();
|
|
||||||
isEditing = true;
|
|
||||||
|
|
||||||
const dateInput = createInput('date');
|
|
||||||
const timeDropdown = createSelect(timesOptions, "시간 선택", 'time-select', 'startAt');
|
|
||||||
const themeDropdown = createSelect(themesOptions, "테마 선택", 'theme-select', 'name');
|
|
||||||
const memberDropdown = createSelect(membersOptions, "멤버 선택", 'member-select', 'name');
|
|
||||||
|
|
||||||
const cellFieldsToCreate = ['', memberDropdown, themeDropdown, dateInput, timeDropdown];
|
|
||||||
|
|
||||||
cellFieldsToCreate.forEach((field, index) => {
|
|
||||||
const cell = row.insertCell(index);
|
|
||||||
if (typeof field === 'string') {
|
|
||||||
cell.textContent = field;
|
|
||||||
} else {
|
|
||||||
cell.appendChild(field);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionCell = row.insertCell(row.cells.length);
|
|
||||||
actionCell.appendChild(createActionButton('확인', 'btn-custom', saveRow));
|
|
||||||
actionCell.appendChild(createActionButton('취소', 'btn-secondary', () => {
|
|
||||||
row.remove();
|
|
||||||
isEditing = false;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function createInput(type) {
|
|
||||||
const input = document.createElement('input');
|
|
||||||
input.type = type;
|
|
||||||
input.className = 'form-control';
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createActionButton(label, className, eventListener) {
|
|
||||||
const button = document.createElement('button');
|
|
||||||
button.textContent = label;
|
|
||||||
button.classList.add('btn', className, 'mr-2');
|
|
||||||
button.addEventListener('click', eventListener);
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveRow(event) {
|
|
||||||
// 이벤트 전파를 막는다
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
const row = event.target.parentNode.parentNode;
|
|
||||||
const dateInput = row.querySelector('input[type="date"]');
|
|
||||||
const memberSelect = row.querySelector('#member-select');
|
|
||||||
const themeSelect = row.querySelector('#theme-select');
|
|
||||||
const timeSelect = row.querySelector('#time-select');
|
|
||||||
|
|
||||||
const reservation = {
|
|
||||||
date: dateInput.value,
|
|
||||||
themeId: themeSelect.value,
|
|
||||||
timeId: timeSelect.value,
|
|
||||||
memberId: memberSelect.value,
|
|
||||||
};
|
|
||||||
|
|
||||||
requestCreate(reservation)
|
|
||||||
.then(() => {
|
|
||||||
location.reload();
|
|
||||||
})
|
|
||||||
.catch(error => console.error('Error:', error));
|
|
||||||
|
|
||||||
isEditing = false; // isEditing 값을 false로 설정
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteRow(event) {
|
|
||||||
const row = event.target.closest('tr');
|
|
||||||
const reservationId = row.cells[0].textContent;
|
|
||||||
|
|
||||||
requestDelete(reservationId)
|
|
||||||
.then(() => row.remove())
|
|
||||||
.catch(error => console.error('Error:', error));
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyFilter(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const themeId = document.getElementById('theme').value;
|
|
||||||
const memberId = document.getElementById('member').value;
|
|
||||||
const dateFrom = document.getElementById('date-from').value;
|
|
||||||
const dateTo = document.getElementById('date-to').value;
|
|
||||||
|
|
||||||
const queryParams = {
|
|
||||||
themeId: themeId,
|
|
||||||
memberId: memberId,
|
|
||||||
dateFrom: dateFrom,
|
|
||||||
dateTo: dateTo
|
|
||||||
}
|
|
||||||
const searchParams = new URLSearchParams(queryParams);
|
|
||||||
const endpoint = '/reservations/search';
|
|
||||||
|
|
||||||
const url = `${endpoint}?${searchParams.toString()}`;
|
|
||||||
fetch(url, { // 예약 검색 API 호출
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
}).then(response => {
|
|
||||||
if (response.status === 200) return response.json();
|
|
||||||
throw new Error('Read failed');
|
|
||||||
}).then(render)
|
|
||||||
.catch(error => console.error("Error fetching available times:", error));
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestCreate(reservation) {
|
|
||||||
const requestOptions = {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify(reservation)
|
|
||||||
};
|
|
||||||
|
|
||||||
return fetch('/reservations/admin', requestOptions)
|
|
||||||
.then(response => {
|
|
||||||
if (response.status === 201) return response.json();
|
|
||||||
throw new Error('Create failed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestDelete(id) {
|
|
||||||
const requestOptions = {
|
|
||||||
method: 'DELETE',
|
|
||||||
};
|
|
||||||
|
|
||||||
return fetch(`${RESERVATION_API_ENDPOINT}/${id}`, requestOptions)
|
|
||||||
.then(response => {
|
|
||||||
if (response.status !== 204) throw new Error('Delete failed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestRead(endpoint) {
|
|
||||||
return fetch(endpoint)
|
|
||||||
.then(response => {
|
|
||||||
if (response.status === 200) return response.json();
|
|
||||||
throw new Error('Read failed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,179 +0,0 @@
|
|||||||
let isEditing = false;
|
|
||||||
const RESERVATION_API_ENDPOINT = '/reservations';
|
|
||||||
const TIME_API_ENDPOINT = '/times';
|
|
||||||
const timesOptions = [];
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
document.getElementById('add-button').addEventListener('click', addInputRow);
|
|
||||||
|
|
||||||
requestRead(RESERVATION_API_ENDPOINT)
|
|
||||||
.then(render)
|
|
||||||
.catch(error => console.error('Error fetching reservations:', error));
|
|
||||||
|
|
||||||
fetchTimes();
|
|
||||||
});
|
|
||||||
|
|
||||||
function render(data) {
|
|
||||||
const tableBody = document.getElementById('table-body');
|
|
||||||
tableBody.innerHTML = '';
|
|
||||||
|
|
||||||
data.data.reservations.forEach(item => {
|
|
||||||
const row = tableBody.insertRow();
|
|
||||||
|
|
||||||
row.insertCell(0).textContent = item.id;
|
|
||||||
row.insertCell(1).textContent = item.name;
|
|
||||||
row.insertCell(2).textContent = item.date;
|
|
||||||
row.insertCell(3).textContent = item.time.startAt;
|
|
||||||
|
|
||||||
const actionCell = row.insertCell(row.cells.length);
|
|
||||||
actionCell.appendChild(createActionButton('삭제', 'btn-danger', deleteRow));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchTimes() {
|
|
||||||
requestRead(TIME_API_ENDPOINT)
|
|
||||||
.then(data => {
|
|
||||||
timesOptions.push(...data.data.times);
|
|
||||||
})
|
|
||||||
.catch(error => console.error('Error fetching time:', error));
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSelect(options, defaultText, selectId, textProperty) {
|
|
||||||
const select = document.createElement('select');
|
|
||||||
select.className = 'form-control';
|
|
||||||
select.id = selectId;
|
|
||||||
|
|
||||||
// 기본 옵션 추가
|
|
||||||
const defaultOption = document.createElement('option');
|
|
||||||
defaultOption.textContent = defaultText;
|
|
||||||
select.appendChild(defaultOption);
|
|
||||||
|
|
||||||
// 넘겨받은 옵션을 바탕으로 드롭다운 메뉴 아이템 생성
|
|
||||||
options.forEach(optionData => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = optionData.id;
|
|
||||||
option.textContent = optionData[textProperty]; // 동적 속성 접근
|
|
||||||
select.appendChild(option);
|
|
||||||
});
|
|
||||||
|
|
||||||
return select;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createActionButton(label, className, eventListener) {
|
|
||||||
const button = document.createElement('button');
|
|
||||||
button.textContent = label;
|
|
||||||
button.classList.add('btn', className, 'mr-2');
|
|
||||||
button.addEventListener('click', eventListener);
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addInputRow() {
|
|
||||||
if (isEditing) return; // 이미 편집 중인 경우 추가하지 않음
|
|
||||||
|
|
||||||
const tableBody = document.getElementById('table-body');
|
|
||||||
const row = tableBody.insertRow();
|
|
||||||
isEditing = true;
|
|
||||||
|
|
||||||
const nameInput = createInput('text');
|
|
||||||
const dateInput = createInput('date');
|
|
||||||
const timeDropdown = createSelect(timesOptions, "시간 선택", 'time-select', 'startAt');
|
|
||||||
|
|
||||||
const cellFieldsToCreate = ['', nameInput, dateInput, timeDropdown];
|
|
||||||
|
|
||||||
cellFieldsToCreate.forEach((field, index) => {
|
|
||||||
const cell = row.insertCell(index);
|
|
||||||
if (typeof field === 'string') {
|
|
||||||
cell.textContent = field;
|
|
||||||
} else {
|
|
||||||
cell.appendChild(field);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionCell = row.insertCell(row.cells.length);
|
|
||||||
actionCell.appendChild(createActionButton('확인', 'btn-custom', saveRow));
|
|
||||||
actionCell.appendChild(createActionButton('취소', 'btn-secondary', () => {
|
|
||||||
row.remove();
|
|
||||||
isEditing = false;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function createInput(type) {
|
|
||||||
const input = document.createElement('input');
|
|
||||||
input.type = type;
|
|
||||||
input.className = 'form-control';
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createActionButton(label, className, eventListener) {
|
|
||||||
const button = document.createElement('button');
|
|
||||||
button.textContent = label;
|
|
||||||
button.classList.add('btn', className, 'mr-2');
|
|
||||||
button.addEventListener('click', eventListener);
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveRow(event) {
|
|
||||||
// 이벤트 전파를 막는다
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
const row = event.target.parentNode.parentNode;
|
|
||||||
const nameInput = row.querySelector('input[type="text"]');
|
|
||||||
const dateInput = row.querySelector('input[type="date"]');
|
|
||||||
const timeSelect = row.querySelector('select');
|
|
||||||
|
|
||||||
const reservation = {
|
|
||||||
name: nameInput.value,
|
|
||||||
date: dateInput.value,
|
|
||||||
timeId: timeSelect.value
|
|
||||||
};
|
|
||||||
|
|
||||||
requestCreate(reservation)
|
|
||||||
.then(() => {
|
|
||||||
location.reload();
|
|
||||||
})
|
|
||||||
.catch(error => console.error('Error:', error));
|
|
||||||
|
|
||||||
isEditing = false; // isEditing 값을 false로 설정
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteRow(event) {
|
|
||||||
const row = event.target.closest('tr');
|
|
||||||
const reservationId = row.cells[0].textContent;
|
|
||||||
|
|
||||||
requestDelete(reservationId)
|
|
||||||
.then(() => row.remove())
|
|
||||||
.catch(error => console.error('Error:', error));
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestCreate(reservation) {
|
|
||||||
const requestOptions = {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify(reservation)
|
|
||||||
};
|
|
||||||
|
|
||||||
return fetch(RESERVATION_API_ENDPOINT, requestOptions)
|
|
||||||
.then(response => {
|
|
||||||
if (response.status === 201) return response.json();
|
|
||||||
throw new Error('Create failed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestDelete(id) {
|
|
||||||
const requestOptions = {
|
|
||||||
method: 'DELETE',
|
|
||||||
};
|
|
||||||
|
|
||||||
return fetch(`${RESERVATION_API_ENDPOINT}/${id}`, requestOptions)
|
|
||||||
.then(response => {
|
|
||||||
if (response.status !== 204) throw new Error('Delete failed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestRead(endpoint) {
|
|
||||||
return fetch(endpoint)
|
|
||||||
.then(response => {
|
|
||||||
if (response.status === 200) return response.json();
|
|
||||||
throw new Error('Read failed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,136 +0,0 @@
|
|||||||
let isEditing = false;
|
|
||||||
const API_ENDPOINT = '/themes';
|
|
||||||
const cellFields = ['id', 'name', 'description', 'thumbnail'];
|
|
||||||
const createCellFields = ['', createInput(), createInput(), createInput()];
|
|
||||||
|
|
||||||
function createBody(inputs) {
|
|
||||||
return {
|
|
||||||
name: inputs[0].value,
|
|
||||||
description: inputs[1].value,
|
|
||||||
thumbnail: inputs[2].value,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
document.getElementById('add-button').addEventListener('click', addRow);
|
|
||||||
requestRead()
|
|
||||||
.then(render)
|
|
||||||
.catch(error => console.error('Error fetching times:', error));
|
|
||||||
});
|
|
||||||
|
|
||||||
function render(data) {
|
|
||||||
const tableBody = document.getElementById('table-body');
|
|
||||||
tableBody.innerHTML = '';
|
|
||||||
|
|
||||||
data.data.themes.forEach(item => {
|
|
||||||
const row = tableBody.insertRow();
|
|
||||||
|
|
||||||
cellFields.forEach((field, index) => {
|
|
||||||
row.insertCell(index).textContent = item[field];
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionCell = row.insertCell(row.cells.length);
|
|
||||||
actionCell.appendChild(createActionButton('삭제', 'btn-danger', deleteRow));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addRow() {
|
|
||||||
if (isEditing) return; // 이미 편집 중인 경우 추가하지 않음
|
|
||||||
|
|
||||||
const tableBody = document.getElementById('table-body');
|
|
||||||
const row = tableBody.insertRow();
|
|
||||||
isEditing = true;
|
|
||||||
|
|
||||||
createAddField(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createAddField(row) {
|
|
||||||
createCellFields.forEach((field, index) => {
|
|
||||||
const cell = row.insertCell(index);
|
|
||||||
if (typeof field === 'string') {
|
|
||||||
cell.textContent = field;
|
|
||||||
} else {
|
|
||||||
cell.appendChild(field);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionCell = row.insertCell(row.cells.length);
|
|
||||||
actionCell.appendChild(createActionButton('확인', 'btn-custom', saveRow));
|
|
||||||
actionCell.appendChild(createActionButton('취소', 'btn-secondary', () => {
|
|
||||||
row.remove();
|
|
||||||
isEditing = false;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function createInput() {
|
|
||||||
const input = document.createElement('input');
|
|
||||||
input.className = 'form-control';
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createActionButton(label, className, eventListener) {
|
|
||||||
const button = document.createElement('button');
|
|
||||||
button.textContent = label;
|
|
||||||
button.classList.add('btn', className, 'mr-2');
|
|
||||||
button.addEventListener('click', eventListener);
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveRow(event) {
|
|
||||||
const row = event.target.parentNode.parentNode;
|
|
||||||
const inputs = row.querySelectorAll('input');
|
|
||||||
const body = createBody(inputs);
|
|
||||||
|
|
||||||
requestCreate(body)
|
|
||||||
.then(() => {
|
|
||||||
location.reload();
|
|
||||||
})
|
|
||||||
.catch(error => console.error('Error:', error));
|
|
||||||
|
|
||||||
isEditing = false; // isEditing 값을 false로 설정
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteRow(event) {
|
|
||||||
const row = event.target.closest('tr');
|
|
||||||
const id = row.cells[0].textContent;
|
|
||||||
|
|
||||||
requestDelete(id)
|
|
||||||
.then(() => row.remove())
|
|
||||||
.catch(error => console.error('Error:', error));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// request
|
|
||||||
|
|
||||||
function requestCreate(data) {
|
|
||||||
const requestOptions = {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify(data)
|
|
||||||
};
|
|
||||||
|
|
||||||
return fetch(API_ENDPOINT, requestOptions)
|
|
||||||
.then(response => {
|
|
||||||
if (response.status === 201) return response.json();
|
|
||||||
throw new Error('Create failed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestRead() {
|
|
||||||
return fetch(API_ENDPOINT)
|
|
||||||
.then(response => {
|
|
||||||
if (response.status === 200) return response.json();
|
|
||||||
throw new Error('Read failed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestDelete(id) {
|
|
||||||
const requestOptions = {
|
|
||||||
method: 'DELETE',
|
|
||||||
};
|
|
||||||
|
|
||||||
return fetch(`${API_ENDPOINT}/${id}`, requestOptions)
|
|
||||||
.then(response => {
|
|
||||||
if (response.status !== 204) throw new Error('Delete failed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,135 +0,0 @@
|
|||||||
let isEditing = false;
|
|
||||||
const API_ENDPOINT = '/times';
|
|
||||||
const cellFields = ['id', 'startAt'];
|
|
||||||
const createCellFields = ['', createInput()];
|
|
||||||
|
|
||||||
function createBody(inputs) {
|
|
||||||
return {
|
|
||||||
startAt: inputs[0].value,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
document.getElementById('add-button').addEventListener('click', addRow);
|
|
||||||
requestRead()
|
|
||||||
.then(render)
|
|
||||||
.catch(error => console.error('Error fetching times:', error));
|
|
||||||
});
|
|
||||||
|
|
||||||
function render(data) {
|
|
||||||
const tableBody = document.getElementById('table-body');
|
|
||||||
tableBody.innerHTML = '';
|
|
||||||
|
|
||||||
data.data.times.forEach(item => {
|
|
||||||
const row = tableBody.insertRow();
|
|
||||||
|
|
||||||
cellFields.forEach((field, index) => {
|
|
||||||
row.insertCell(index).textContent = item[field];
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionCell = row.insertCell(row.cells.length);
|
|
||||||
actionCell.appendChild(createActionButton('삭제', 'btn-danger', deleteRow));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addRow() {
|
|
||||||
if (isEditing) return; // 이미 편집 중인 경우 추가하지 않음
|
|
||||||
|
|
||||||
const tableBody = document.getElementById('table-body');
|
|
||||||
const row = tableBody.insertRow();
|
|
||||||
isEditing = true;
|
|
||||||
|
|
||||||
createAddField(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createAddField(row) {
|
|
||||||
createCellFields.forEach((field, index) => {
|
|
||||||
const cell = row.insertCell(index);
|
|
||||||
if (typeof field === 'string') {
|
|
||||||
cell.textContent = field;
|
|
||||||
} else {
|
|
||||||
cell.appendChild(field);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionCell = row.insertCell(row.cells.length);
|
|
||||||
actionCell.appendChild(createActionButton('확인', 'btn-custom', saveRow));
|
|
||||||
actionCell.appendChild(createActionButton('취소', 'btn-secondary', () => {
|
|
||||||
row.remove();
|
|
||||||
isEditing = false;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function createInput() {
|
|
||||||
const input = document.createElement('input');
|
|
||||||
input.type = 'time'
|
|
||||||
input.className = 'form-control';
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createActionButton(label, className, eventListener) {
|
|
||||||
const button = document.createElement('button');
|
|
||||||
button.textContent = label;
|
|
||||||
button.classList.add('btn', className, 'mr-2');
|
|
||||||
button.addEventListener('click', eventListener);
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveRow(event) {
|
|
||||||
const row = event.target.parentNode.parentNode;
|
|
||||||
const inputs = row.querySelectorAll('input');
|
|
||||||
const body = createBody(inputs);
|
|
||||||
|
|
||||||
requestCreate(body)
|
|
||||||
.then(() => {
|
|
||||||
location.reload();
|
|
||||||
})
|
|
||||||
.catch(error => console.error('Error:', error));
|
|
||||||
|
|
||||||
isEditing = false; // isEditing 값을 false로 설정
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteRow(event) {
|
|
||||||
const row = event.target.closest('tr');
|
|
||||||
const id = row.cells[0].textContent;
|
|
||||||
|
|
||||||
requestDelete(id)
|
|
||||||
.then(() => row.remove())
|
|
||||||
.catch(error => console.error('Error:', error));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// request
|
|
||||||
|
|
||||||
function requestCreate(data) {
|
|
||||||
const requestOptions = {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify(data)
|
|
||||||
};
|
|
||||||
|
|
||||||
return fetch(API_ENDPOINT, requestOptions)
|
|
||||||
.then(response => {
|
|
||||||
if (response.status === 201) return response.json();
|
|
||||||
throw new Error('Create failed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestRead() {
|
|
||||||
return fetch(API_ENDPOINT)
|
|
||||||
.then(response => {
|
|
||||||
if (response.status === 200) return response.json();
|
|
||||||
throw new Error('Read failed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestDelete(id) {
|
|
||||||
const requestOptions = {
|
|
||||||
method: 'DELETE',
|
|
||||||
};
|
|
||||||
|
|
||||||
return fetch(`${API_ENDPOINT}/${id}`, requestOptions)
|
|
||||||
.then(response => {
|
|
||||||
if (response.status !== 204) throw new Error('Delete failed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,273 +0,0 @@
|
|||||||
const THEME_API_ENDPOINT = '/themes';
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
requestRead(THEME_API_ENDPOINT)
|
|
||||||
.then(renderTheme)
|
|
||||||
.catch(error => console.error('Error fetching times:', error));
|
|
||||||
|
|
||||||
flatpickr("#datepicker", {
|
|
||||||
inline: true,
|
|
||||||
onChange: function (selectedDates, dateStr, instance) {
|
|
||||||
if (dateStr === '') return;
|
|
||||||
checkDate();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ------ 결제위젯 초기화 ------
|
|
||||||
// @docs https://docs.tosspayments.com/reference/widget-sdk#sdk-설치-및-초기화
|
|
||||||
// @docs https://docs.tosspayments.com/reference/widget-sdk#renderpaymentmethods선택자-결제-금액-옵션
|
|
||||||
const paymentAmount = 1000;
|
|
||||||
const widgetClientKey = "test_gck_docs_Ovk5rk1EwkEbP0W43n07xlzm";
|
|
||||||
const paymentWidget = PaymentWidget(widgetClientKey, PaymentWidget.ANONYMOUS);
|
|
||||||
paymentWidget.renderPaymentMethods(
|
|
||||||
"#payment-method",
|
|
||||||
{value: paymentAmount},
|
|
||||||
{variantKey: "DEFAULT"}
|
|
||||||
);
|
|
||||||
|
|
||||||
document.getElementById('theme-slots').addEventListener('click', event => {
|
|
||||||
if (event.target.classList.contains('theme-slot')) {
|
|
||||||
document.querySelectorAll('.theme-slot').forEach(slot => slot.classList.remove('active'));
|
|
||||||
event.target.classList.add('active');
|
|
||||||
checkDateAndTheme();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('time-slots').addEventListener('click', event => {
|
|
||||||
if (event.target.classList.contains('time-slot') && !event.target.classList.contains('disabled')) {
|
|
||||||
document.querySelectorAll('.time-slot').forEach(slot => slot.classList.remove('active'));
|
|
||||||
event.target.classList.add('active');
|
|
||||||
checkDateAndThemeAndTime();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('reserve-button').addEventListener('click', onReservationButtonClickWithPaymentWidget);
|
|
||||||
document.getElementById('wait-button').addEventListener('click', onWaitButtonClick);
|
|
||||||
|
|
||||||
function onReservationButtonClickWithPaymentWidget(event) {
|
|
||||||
onReservationButtonClick(event, paymentWidget);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function renderTheme(themes) {
|
|
||||||
const themeSlots = document.getElementById('theme-slots');
|
|
||||||
themeSlots.innerHTML = '';
|
|
||||||
themes.data.themes.forEach(theme => {
|
|
||||||
const name = theme.name;
|
|
||||||
const themeId = theme.id;
|
|
||||||
themeSlots.appendChild(createSlot('theme', name, themeId));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSlot(type, text, id, booked) {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.className = type + '-slot cursor-pointer bg-light border rounded p-3 mb-2';
|
|
||||||
div.textContent = text;
|
|
||||||
div.setAttribute('data-' + type + '-id', id);
|
|
||||||
if (type === 'time') {
|
|
||||||
div.setAttribute('data-time-booked', booked);
|
|
||||||
}
|
|
||||||
return div;
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkDate() {
|
|
||||||
const selectedDate = document.getElementById("datepicker").value;
|
|
||||||
if (selectedDate) {
|
|
||||||
const themeSection = document.getElementById("theme-section");
|
|
||||||
if (themeSection.classList.contains("disabled")) {
|
|
||||||
themeSection.classList.remove("disabled");
|
|
||||||
}
|
|
||||||
const timeSlots = document.getElementById('time-slots');
|
|
||||||
timeSlots.innerHTML = '';
|
|
||||||
|
|
||||||
requestRead(THEME_API_ENDPOINT)
|
|
||||||
.then(renderTheme)
|
|
||||||
.catch(error => console.error('Error fetching times:', error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkDateAndTheme() {
|
|
||||||
const selectedDate = document.getElementById("datepicker").value;
|
|
||||||
const selectedThemeElement = document.querySelector('.theme-slot.active');
|
|
||||||
if (selectedDate && selectedThemeElement) {
|
|
||||||
const selectedThemeId = selectedThemeElement.getAttribute('data-theme-id');
|
|
||||||
fetchAvailableTimes(selectedDate, selectedThemeId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchAvailableTimes(date, themeId) {
|
|
||||||
|
|
||||||
fetch(`/times/search?date=${date}&themeId=${themeId}`, { // 예약 가능 시간 조회 API endpoint
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
}
|
|
||||||
}).then(response => {
|
|
||||||
if (response.status === 200) return response.json();
|
|
||||||
throw new Error('Read failed');
|
|
||||||
}).then(renderAvailableTimes)
|
|
||||||
.catch(error => console.error("Error fetching available times:", error));
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderAvailableTimes(times) {
|
|
||||||
const timeSection = document.getElementById("time-section");
|
|
||||||
if (timeSection.classList.contains("disabled")) {
|
|
||||||
timeSection.classList.remove("disabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeSlots = document.getElementById('time-slots');
|
|
||||||
timeSlots.innerHTML = '';
|
|
||||||
if (times.length === 0) {
|
|
||||||
timeSlots.innerHTML = '<div class="no-times">선택할 수 있는 시간이 없습니다.</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
times.data.times.forEach(time => {
|
|
||||||
const startAt = time.startAt;
|
|
||||||
const timeId = time.id;
|
|
||||||
const isAvailable = time.isAvailable;
|
|
||||||
|
|
||||||
const div = createSlot('time', startAt, timeId, isAvailable); // createSlot('time', 시작 시간, time id, 예약 여부)
|
|
||||||
timeSlots.appendChild(div);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkDateAndThemeAndTime() {
|
|
||||||
const selectedDate = document.getElementById("datepicker").value;
|
|
||||||
const selectedThemeElement = document.querySelector('.theme-slot.active');
|
|
||||||
const selectedTimeElement = document.querySelector('.time-slot.active');
|
|
||||||
const reserveButton = document.getElementById("reserve-button");
|
|
||||||
const waitButton = document.getElementById("wait-button");
|
|
||||||
|
|
||||||
if (selectedDate && selectedThemeElement && selectedTimeElement) {
|
|
||||||
if (selectedTimeElement.getAttribute('data-time-booked') === 'false') {
|
|
||||||
// 선택된 시간이 이미 예약된 경우
|
|
||||||
reserveButton.classList.add("disabled");
|
|
||||||
waitButton.classList.remove("disabled"); // 예약 대기 버튼 활성화
|
|
||||||
} else {
|
|
||||||
// 선택된 시간이 예약 가능한 경우
|
|
||||||
reserveButton.classList.remove("disabled");
|
|
||||||
waitButton.classList.add("disabled"); // 예약 대기 버튼 활성화
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 날짜, 테마, 시간 중 하나라도 선택되지 않은 경우
|
|
||||||
reserveButton.classList.add("disabled");
|
|
||||||
waitButton.classList.add("disabled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onReservationButtonClick(event, paymentWidget) {
|
|
||||||
const selectedDate = document.getElementById("datepicker").value;
|
|
||||||
const selectedThemeId = document.querySelector('.theme-slot.active')?.getAttribute('data-theme-id');
|
|
||||||
const selectedTimeId = document.querySelector('.time-slot.active')?.getAttribute('data-time-id');
|
|
||||||
|
|
||||||
if (selectedDate && selectedThemeId && selectedTimeId) {
|
|
||||||
const reservationData = {
|
|
||||||
date: selectedDate,
|
|
||||||
themeId: selectedThemeId,
|
|
||||||
timeId: selectedTimeId,
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateRandomString = () =>
|
|
||||||
window.btoa(Math.random()).slice(0, 20);
|
|
||||||
|
|
||||||
// TOSS 결제 위젯 Javascript SDK 연동 방식 중 'Promise로 처리하기'를 적용함
|
|
||||||
// https://docs.tosspayments.com/reference/widget-sdk#promise%EB%A1%9C-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0
|
|
||||||
const orderIdPrefix = "WTEST";
|
|
||||||
paymentWidget.requestPayment({
|
|
||||||
orderId: orderIdPrefix + generateRandomString(),
|
|
||||||
orderName: "테스트 방탈출 예약 결제 1건",
|
|
||||||
amount: 1000,
|
|
||||||
}).then(function (data) {
|
|
||||||
console.debug(data);
|
|
||||||
fetchReservationPayment(data, reservationData);
|
|
||||||
}).catch(function (error) {
|
|
||||||
// TOSS 에러 처리: 에러 목록을 확인하세요
|
|
||||||
// https://docs.tosspayments.com/reference/error-codes#failurl 로-전달되는-에러
|
|
||||||
alert(error.code + " :" + error.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
alert("Please select a date, theme, and time before making a reservation.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchReservationPayment(paymentData, reservationData) {
|
|
||||||
const reservationPaymentRequest = {
|
|
||||||
date: reservationData.date,
|
|
||||||
themeId: reservationData.themeId,
|
|
||||||
timeId: reservationData.timeId,
|
|
||||||
paymentKey: paymentData.paymentKey,
|
|
||||||
orderId: paymentData.orderId,
|
|
||||||
amount: paymentData.amount,
|
|
||||||
paymentType: paymentData.paymentType,
|
|
||||||
}
|
|
||||||
|
|
||||||
const reservationURL = "/reservations";
|
|
||||||
fetch(reservationURL, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(reservationPaymentRequest),
|
|
||||||
}).then(response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
return response.json().then(errorBody => {
|
|
||||||
console.error("예약 결제 실패 : " + JSON.stringify(errorBody));
|
|
||||||
window.alert("예약 결제 실패 메시지: " + errorBody.message);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
response.json().then(successBody => {
|
|
||||||
alert("예약이 완료되었습니다.");
|
|
||||||
console.log("예약 결제 성공 : " + JSON.stringify(successBody));
|
|
||||||
window.location.href = "/";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).catch(error => {
|
|
||||||
console.error(error.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onWaitButtonClick() {
|
|
||||||
const selectedDate = document.getElementById("datepicker").value;
|
|
||||||
const selectedThemeId = document.querySelector('.theme-slot.active')?.getAttribute('data-theme-id');
|
|
||||||
const selectedTimeId = document.querySelector('.time-slot.active')?.getAttribute('data-time-id');
|
|
||||||
|
|
||||||
if (selectedDate && selectedThemeId && selectedTimeId) {
|
|
||||||
const reservationData = {
|
|
||||||
date: selectedDate,
|
|
||||||
timeId: selectedTimeId,
|
|
||||||
themeId: selectedThemeId,
|
|
||||||
};
|
|
||||||
|
|
||||||
fetch('/reservations/waiting', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(reservationData)
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) throw new Error('Reservation waiting failed');
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
alert('Reservation waiting successful!');
|
|
||||||
window.location.href = "/";
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
alert("An error occurred while making the reservation waiting.");
|
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
alert("Please select a date, theme, and time before making a reservation waiting.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function requestRead(endpoint) {
|
|
||||||
return fetch(endpoint)
|
|
||||||
.then(response => {
|
|
||||||
if (response.status === 200) return response.json();
|
|
||||||
throw new Error('Read failed');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,152 +0,0 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
updateUIBasedOnLogin();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('logout-btn').addEventListener('click', function (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
fetch('/logout', {
|
|
||||||
method: 'POST', // 또는 서버 설정에 따라 GET 일 수도 있음
|
|
||||||
credentials: 'include' // 쿠키를 포함시키기 위해 필요
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
// 로그아웃 성공, 페이지 새로고침 또는 리다이렉트
|
|
||||||
window.location.reload();
|
|
||||||
} else {
|
|
||||||
// 로그아웃 실패 처리
|
|
||||||
console.error('Logout failed');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error:', error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateUIBasedOnLogin() {
|
|
||||||
fetch('/login/check') // 로그인 상태 확인 API 호출
|
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) { // 요청이 실패하거나 로그인 상태가 아닌 경우
|
|
||||||
throw new Error('Not logged in or other error');
|
|
||||||
}
|
|
||||||
return response.json(); // 응답 본문을 JSON으로 파싱
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
// 응답에서 사용자 이름을 추출하여 UI 업데이트
|
|
||||||
document.getElementById('profile-name').textContent = data.data.name; // 프로필 이름 설정
|
|
||||||
document.querySelector('.nav-item.dropdown').style.display = 'block'; // 드롭다운 메뉴 표시
|
|
||||||
document.querySelector('.nav-item a[href="/login"]').parentElement.style.display = 'none'; // 로그인 버튼 숨김
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
// 에러 처리 또는 로그아웃 상태일 때 UI 업데이트
|
|
||||||
console.error('Error:', error);
|
|
||||||
document.getElementById('profile-name').textContent = 'Profile'; // 기본 텍스트로 재설정
|
|
||||||
document.querySelector('.nav-item.dropdown').style.display = 'none'; // 드롭다운 메뉴 숨김
|
|
||||||
document.querySelector('.nav-item a[href="/login"]').parentElement.style.display = 'block'; // 로그인 버튼 표시
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 드롭다운 메뉴 토글
|
|
||||||
document.getElementById("navbarDropdown").addEventListener('click', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const dropdownMenu = e.target.closest('.nav-item.dropdown').querySelector('.dropdown-menu');
|
|
||||||
dropdownMenu.classList.toggle('show'); // Bootstrap 4에서는 data-toggle 사용, Bootstrap 5에서는 JS로 처리
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
function login() {
|
|
||||||
const email = document.getElementById('email').value;
|
|
||||||
const password = document.getElementById('password').value;
|
|
||||||
|
|
||||||
// 입력 필드 검증
|
|
||||||
if (!email || !password) {
|
|
||||||
alert('Please fill in all fields.');
|
|
||||||
return; // 필수 입력 필드가 비어있으면 여기서 함수 실행을 중단
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch('/login', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email: email,
|
|
||||||
password: password
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (200 !== response.status) {
|
|
||||||
alert('Login failed'); // 로그인 실패 시 경고창 표시
|
|
||||||
throw new Error('Login failed');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
updateUIBasedOnLogin(); // UI 업데이트
|
|
||||||
window.location.href = '/';
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error during login:', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function signup() {
|
|
||||||
// Redirect to signup page
|
|
||||||
window.location.href = '/signup';
|
|
||||||
}
|
|
||||||
|
|
||||||
function register(event) {
|
|
||||||
// 폼 데이터 수집
|
|
||||||
const email = document.getElementById('email').value;
|
|
||||||
const password = document.getElementById('password').value;
|
|
||||||
const name = document.getElementById('name').value;
|
|
||||||
|
|
||||||
// 입력 필드 검증
|
|
||||||
if (!email || !password || !name) {
|
|
||||||
alert('Please fill in all fields.');
|
|
||||||
return; // 필수 입력 필드가 비어있으면 여기서 함수 실행을 중단
|
|
||||||
}
|
|
||||||
|
|
||||||
// 요청 데이터 포맷팅
|
|
||||||
const formData = {
|
|
||||||
email: email,
|
|
||||||
password: password,
|
|
||||||
name: name
|
|
||||||
};
|
|
||||||
|
|
||||||
// AJAX 요청 생성 및 전송
|
|
||||||
fetch('/members', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(formData)
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
alert('Signup request failed');
|
|
||||||
throw new Error('Signup request failed');
|
|
||||||
}
|
|
||||||
return response.json(); // 여기서 응답을 JSON 형태로 변환
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
// 성공적인 응답 처리
|
|
||||||
console.log('Signup successful:', data);
|
|
||||||
window.location.href = '/login';
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
// 에러 처리
|
|
||||||
console.error('Error during signup:', error);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 폼 제출에 의한 페이지 리로드 방지
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
function base64DecodeUnicode(str) {
|
|
||||||
// Base64 디코딩
|
|
||||||
const decodedBytes = atob(str);
|
|
||||||
// UTF-8 바이트를 문자열로 변환
|
|
||||||
const encodedUriComponent = decodedBytes.split('').map(function (c) {
|
|
||||||
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
|
||||||
}).join('');
|
|
||||||
return decodeURIComponent(encodedUriComponent);
|
|
||||||
}
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
fetch('/reservations/waiting') // 내 예약 목록 조회 API 호출
|
|
||||||
.then(response => {
|
|
||||||
if (response.status === 200) return response.json();
|
|
||||||
throw new Error('Read failed');
|
|
||||||
})
|
|
||||||
.then(render)
|
|
||||||
.catch(error => console.error('Error fetching reservations:', error));
|
|
||||||
});
|
|
||||||
|
|
||||||
function render(data) {
|
|
||||||
const tableBody = document.getElementById('table-body');
|
|
||||||
tableBody.innerHTML = '';
|
|
||||||
|
|
||||||
data.data.reservations.forEach(item => {
|
|
||||||
const row = tableBody.insertRow();
|
|
||||||
|
|
||||||
const id = item.id;
|
|
||||||
const name = item.member.name;
|
|
||||||
const theme = item.theme.name;
|
|
||||||
const date = item.date;
|
|
||||||
const startAt = item.time.startAt;
|
|
||||||
|
|
||||||
row.insertCell(0).textContent = id; // 예약 대기 id
|
|
||||||
row.insertCell(1).textContent = name; // 예약자명
|
|
||||||
row.insertCell(2).textContent = theme; // 테마명
|
|
||||||
row.insertCell(3).textContent = date; // 예약 날짜
|
|
||||||
row.insertCell(4).textContent = startAt; // 시작 시간
|
|
||||||
|
|
||||||
const actionCell = row.insertCell(row.cells.length);
|
|
||||||
|
|
||||||
actionCell.appendChild(createActionButton('승인', 'btn-primary', approve));
|
|
||||||
actionCell.appendChild(createActionButton('거절', 'btn-danger', deny));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function approve(event) {
|
|
||||||
const row = event.target.closest('tr');
|
|
||||||
const id = row.cells[0].textContent;
|
|
||||||
|
|
||||||
const endpoint = `/reservations/waiting/${id}/confirm`
|
|
||||||
return fetch(endpoint, {
|
|
||||||
method: 'POST'
|
|
||||||
}).then(response => {
|
|
||||||
if (response.status === 200) return;
|
|
||||||
throw new Error('Delete failed');
|
|
||||||
}).then(() => location.reload());
|
|
||||||
}
|
|
||||||
|
|
||||||
function deny(event) {
|
|
||||||
const row = event.target.closest('tr');
|
|
||||||
const id = row.cells[0].textContent;
|
|
||||||
|
|
||||||
const endpoint = `/reservations/waiting/${id}/reject`
|
|
||||||
return fetch(endpoint, {
|
|
||||||
method: 'POST'
|
|
||||||
}).then(response => {
|
|
||||||
if (response.status === 204) return;
|
|
||||||
throw new Error('Delete failed');
|
|
||||||
}).then(() => location.reload());
|
|
||||||
}
|
|
||||||
|
|
||||||
function createActionButton(label, className, eventListener) {
|
|
||||||
const button = document.createElement('button');
|
|
||||||
button.textContent = label;
|
|
||||||
button.classList.add('btn', className, 'mr-2');
|
|
||||||
button.addEventListener('click', eventListener);
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>방탈출 어드민</title>
|
|
||||||
<!-- Bootstrap CSS -->
|
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
|
||||||
<a class="navbar-brand" href="/admin">
|
|
||||||
<img src="/image/admin-logo.png" alt="LOGO" style="height: 40px;">
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
|
|
||||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
|
||||||
<ul class="navbar-nav ml-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/reservation">Reservation</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/waiting">Waiting</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/theme">Theme</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/time">Time</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/login">Login</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown"
|
|
||||||
aria-haspopup="true" aria-expanded="false">
|
|
||||||
<img class="profile-image" src="/image/default-profile.png" alt="Profile">
|
|
||||||
<span id="profile-name">Profile</span> <!-- 프로필 이름을 넣을 span 추가 -->
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
|
||||||
<a class="dropdown-item" href="/reservation-mine">My Reservation</a>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item" href="#" id="logout-btn">Logout</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="content-container">
|
|
||||||
<h2 class="content-container-title">방탈출 어드민</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/js/user-scripts.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,111 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>방탈출 어드민</title>
|
|
||||||
<!-- Bootstrap CSS -->
|
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
|
||||||
<a class="navbar-brand" href="/admin">
|
|
||||||
<img src="/image/admin-logo.png" alt="LOGO" style="height: 40px;">
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
|
|
||||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
|
||||||
<ul class="navbar-nav ml-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/reservation">Reservation</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/waiting">Waiting</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/theme">Theme</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/time">Time</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/login">Login</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown"
|
|
||||||
aria-haspopup="true" aria-expanded="false">
|
|
||||||
<img class="profile-image" src="/image/default-profile.png" alt="Profile">
|
|
||||||
<span id="profile-name">Profile</span> <!-- 프로필 이름을 넣을 span 추가 -->
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
|
||||||
<a class="dropdown-item" href="/reservation-mine">My Reservation</a>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item" href="#" id="logout-btn">Logout</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="content-container">
|
|
||||||
<h2 class="content-container-title">방탈출 예약 페이지</h2>
|
|
||||||
<div class="d-flex">
|
|
||||||
<div class="table-container flex-grow-1 mr-3">
|
|
||||||
<div class="table-header d-flex justify-content-end">
|
|
||||||
<button id="add-button" class="btn btn-custom mb-2">예약 추가</button>
|
|
||||||
</div>
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>예약번호</th>
|
|
||||||
<th>예약자</th>
|
|
||||||
<th>테마</th>
|
|
||||||
<th>날짜</th>
|
|
||||||
<th>시간</th>
|
|
||||||
<th>결제 완료 여부</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="table-body">
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="filter-section ml-3">
|
|
||||||
<form id="filter-form">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="member">예약자</label>
|
|
||||||
<select type="text" id="member" name="member" class="form-control">
|
|
||||||
<option value="">전체</option>
|
|
||||||
<option style="display:none"></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="theme">테마</label>
|
|
||||||
<select type="text" id="theme" name="theme" class="form-control">
|
|
||||||
<option value="">전체</option>
|
|
||||||
<option style="display:none"></option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="date-from">From</label>
|
|
||||||
<input type="date" id="date-from" name="date-from" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="date-to">To</label>
|
|
||||||
<input type="date" id="date-to" name="date-to" class="form-control">
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary float-right">적용</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/js/user-scripts.js"></script>
|
|
||||||
<script src="/js/reservation-with-member.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>방탈출 어드민</title>
|
|
||||||
<!-- Bootstrap CSS -->
|
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
|
||||||
<a class="navbar-brand" href="/admin">
|
|
||||||
<img src="/image/admin-logo.png" alt="LOGO" style="height: 40px;">
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
|
|
||||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
|
||||||
<ul class="navbar-nav ml-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/reservation">Reservation</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/waiting">Waiting</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/theme">Theme</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/time">Time</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="content-container">
|
|
||||||
<h2 class="content-container-title">방탈출 예약 페이지</h2>
|
|
||||||
<div class="table-header">
|
|
||||||
<button id="add-button" class="btn btn-custom mb-2 float-right">예약 추가</button>
|
|
||||||
</div>
|
|
||||||
<div class="table-container"></div>
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>예약번호</th>
|
|
||||||
<th>예약자</th>
|
|
||||||
<th>날짜</th>
|
|
||||||
<th>시간</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="table-body">
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/js/reservation.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,80 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>방탈출 어드민</title>
|
|
||||||
<!-- Bootstrap CSS -->
|
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
|
||||||
<a class="navbar-brand" href="/admin">
|
|
||||||
<img src="/image/admin-logo.png" alt="LOGO" style="height: 40px;">
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
|
|
||||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
|
||||||
<ul class="navbar-nav ml-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/reservation">Reservation</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/waiting">Waiting</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/theme">Theme</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/time">Time</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/login">Login</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown"
|
|
||||||
aria-haspopup="true" aria-expanded="false">
|
|
||||||
<img class="profile-image" src="/image/default-profile.png" alt="Profile">
|
|
||||||
<span id="profile-name">Profile</span> <!-- 프로필 이름을 넣을 span 추가 -->
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
|
||||||
<a class="dropdown-item" href="/reservation-mine">My Reservation</a>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item" href="#" id="logout-btn">Logout</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="content-container">
|
|
||||||
<h2 class="content-container-title">테마 관리 페이지</h2>
|
|
||||||
<div class="table-header">
|
|
||||||
<button id="add-button" class="btn btn-custom mb-2 float-right">테마 추가</button>
|
|
||||||
</div>
|
|
||||||
<div class="table-container"/>
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">순서</th>
|
|
||||||
<th scope="col">제목</th>
|
|
||||||
<th scope="col">설명</th>
|
|
||||||
<th scope="col">썸네일 URL</th>
|
|
||||||
<th scope="col"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="table-body">
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/js/user-scripts.js"></script>
|
|
||||||
|
|
||||||
<script src="/js/theme.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>방탈출 어드민</title>
|
|
||||||
<!-- Bootstrap CSS -->
|
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
|
||||||
<a class="navbar-brand" href="/admin">
|
|
||||||
<img src="/image/admin-logo.png" alt="LOGO" style="height: 40px;">
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
|
|
||||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
|
||||||
<ul class="navbar-nav ml-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/reservation">Reservation</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/waiting">Waiting</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/theme">Theme</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/time">Time</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/login">Login</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown"
|
|
||||||
aria-haspopup="true" aria-expanded="false">
|
|
||||||
<img class="profile-image" src="/image/default-profile.png" alt="Profile">
|
|
||||||
<span id="profile-name">Profile</span> <!-- 프로필 이름을 넣을 span 추가 -->
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
|
||||||
<a class="dropdown-item" href="/reservation-mine">My Reservation</a>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item" href="#" id="logout-btn">Logout</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="content-container">
|
|
||||||
<h2 class="content-container-title">시간 관리 페이지</h2>
|
|
||||||
<div class="table-header">
|
|
||||||
<button id="add-button" class="btn btn-custom mb-2 float-right">예약시간 추가</button>
|
|
||||||
</div>
|
|
||||||
<div class="table-container"/>
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">순서</th>
|
|
||||||
<th scope="col">시간</th>
|
|
||||||
<th scope="col"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="table-body">
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/js/user-scripts.js"></script>
|
|
||||||
|
|
||||||
<script src="/js/time.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>방탈출 어드민</title>
|
|
||||||
<!-- Bootstrap CSS -->
|
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
|
||||||
<a class="navbar-brand" href="/admin">
|
|
||||||
<img src="/image/admin-logo.png" alt="LOGO" style="height: 40px;">
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
|
|
||||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
|
||||||
<ul class="navbar-nav ml-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/reservation">Reservation</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/waiting">Waiting</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/theme">Theme</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/admin/time">Time</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/login">Login</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown"
|
|
||||||
aria-haspopup="true" aria-expanded="false">
|
|
||||||
<img class="profile-image" src="/image/default-profile.png" alt="Profile">
|
|
||||||
<span id="profile-name">Profile</span> <!-- 프로필 이름을 넣을 span 추가 -->
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
|
||||||
<a class="dropdown-item" href="/reservation-mine">My Reservation</a>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item" href="#" id="logout-btn">Logout</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="content-container">
|
|
||||||
<h2 class="content-container-title">예약 대기 관리 페이지</h2>
|
|
||||||
<div class="table-container"/>
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">예약대기 번호</th>
|
|
||||||
<th scope="col">예약자</th>
|
|
||||||
<th scope="col">테마</th>
|
|
||||||
<th scope="col">날짜</th>
|
|
||||||
<th scope="col">시간</th>
|
|
||||||
<th scope="col"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="table-body">
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/js/user-scripts.js"></script>
|
|
||||||
<script src="/js/waiting.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>방탈출 예약 페이지</title>
|
|
||||||
<!-- Bootstrap CSS -->
|
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
|
||||||
<a class="navbar-brand" href="/">
|
|
||||||
<img src="https://avatars.githubusercontent.com/u/141792611?s=200&v=4" alt="LOGO" style="height: 40px;">
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
|
|
||||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
|
||||||
<ul class="navbar-nav ml-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/reservation">Reservation</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/login">Login</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown"
|
|
||||||
aria-haspopup="true" aria-expanded="false">
|
|
||||||
<img class="profile-image" src="/image/default-profile.png" alt="Profile">
|
|
||||||
<span id="profile-name">Profile</span> <!-- 프로필 이름을 넣을 span 추가 -->
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
|
||||||
<a class="dropdown-item" href="/reservation-mine">My Reservation</a>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item" href="#" id="logout-btn">Logout</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="content-container">
|
|
||||||
<h2 class="content-container-title">인기 테마</h2>
|
|
||||||
<ul class="list-unstyled" id="theme-ranking">
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/js/user-scripts.js"></script>
|
|
||||||
|
|
||||||
<script src="/js/ranking.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Login</title>
|
|
||||||
<!-- Bootstrap CSS -->
|
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
|
||||||
<a class="navbar-brand" href="/">
|
|
||||||
<img src="https://avatars.githubusercontent.com/u/141792611?s=200&v=4" alt="LOGO" style="height: 40px;">
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
|
|
||||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
|
||||||
<ul class="navbar-nav ml-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/reservation">Reservation</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/login">Login</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown"
|
|
||||||
aria-haspopup="true" aria-expanded="false">
|
|
||||||
<img class="profile-image" src="/image/default-profile.png" alt="Profile">
|
|
||||||
<span id="profile-name">Profile</span> <!-- 프로필 이름을 넣을 span 추가 -->
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
|
||||||
<a class="dropdown-item" href="/reservation-mine">My Reservation</a>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item" href="#" id="logout-btn">Logout</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="content-container" style="width: 300px;">
|
|
||||||
<h2 class="content-container-title">Login</h2>
|
|
||||||
<form id="login-form">
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="email" class="form-control" id="email" placeholder="Email" required>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="password" class="form-control" id="password" placeholder="Password" required>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<button type="button" class="btn btn-outline-custom" onclick="signup()">Sign Up</button>
|
|
||||||
<button type="button" class="btn btn-custom" onclick="login()">Login</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/js/user-scripts.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>방탈출 어드민</title>
|
|
||||||
<!-- Bootstrap CSS -->
|
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
|
||||||
<a class="navbar-brand" href="/">
|
|
||||||
<img src="https://avatars.githubusercontent.com/u/141792611?s=200&v=4" alt="LOGO" style="height: 40px;">
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
|
|
||||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
|
||||||
<ul class="navbar-nav ml-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/reservation">Reservation</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/login">Login</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown"
|
|
||||||
aria-haspopup="true" aria-expanded="false">
|
|
||||||
<img class="profile-image" src="/image/default-profile.png" alt="Profile">
|
|
||||||
<span id="profile-name">Profile</span> <!-- 프로필 이름을 넣을 span 추가 -->
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
|
||||||
<a class="dropdown-item" href="/reservation-mine">My Reservation</a>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item" href="#" id="logout-btn">Logout</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="content-container">
|
|
||||||
<h2 class="content-container-title">내 예약</h2>
|
|
||||||
<div class="table-container"></div>
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>테마</th>
|
|
||||||
<th>날짜</th>
|
|
||||||
<th>시간</th>
|
|
||||||
<th>상태</th>
|
|
||||||
<th>대기 취소</th>
|
|
||||||
<th>paymentKey</th>
|
|
||||||
<th>결제금액</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="table-body">
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/js/user-scripts.js"></script>
|
|
||||||
<script src="/js/reservation-mine.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,103 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>방탈출 예약 페이지</title>
|
|
||||||
<!-- Bootstrap CSS -->
|
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
|
|
||||||
<link rel="stylesheet" href="/css/reservation.css">
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
<link rel="stylesheet" href="/css/toss-style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
|
||||||
<a class="navbar-brand" href="/">
|
|
||||||
<img src="https://avatars.githubusercontent.com/u/141792611?s=200&v=4" alt="LOGO" style="height: 40px;">
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
|
|
||||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
|
||||||
<ul class="navbar-nav ml-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/reservation">Reservation</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/login">Login</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown"
|
|
||||||
aria-haspopup="true" aria-expanded="false">
|
|
||||||
<img class="profile-image" src="/image/default-profile.png" alt="Profile">
|
|
||||||
<span id="profile-name">Profile</span> <!-- 프로필 이름을 넣을 span 추가 -->
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
|
||||||
<a class="dropdown-item" href="/reservation-mine">My Reservation</a>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item" href="#" id="logout-btn">Logout</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- Content -->
|
|
||||||
<div id="content-container" class="col-md-10 offset-md-1 p-5">
|
|
||||||
<h2 class="content-container-title">예약 페이지</h2>
|
|
||||||
<div class="d-flex" id="reservation-container">
|
|
||||||
<!-- Date Section -->
|
|
||||||
<div class="section border rounded col-md-4 p-3" id="date-section">
|
|
||||||
<h3 class="fs-5 text-center mb-3">날짜 선택</h3>
|
|
||||||
<div class="d-flex justify-content-center p-3">
|
|
||||||
<div id="datepicker"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Theme Section -->
|
|
||||||
<div class="section border rounded col-md-4 p-3 disabled" id="theme-section">
|
|
||||||
<h3 class="fs-5 text-center mb-3">테마 선택</h3>
|
|
||||||
<div class="p-3" id="theme-slots">
|
|
||||||
<!-- Dynamically generated theme slots will go here -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Time Section -->
|
|
||||||
<div class="section border rounded col-md-4 p-3 disabled" id="time-section">
|
|
||||||
<h3 class="fs-5 text-center mb-3">시간 선택</h3>
|
|
||||||
<div class="p-3" id="time-slots">
|
|
||||||
<!-- Dynamically generated time slots will go here -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Reservation Button -->
|
|
||||||
<div class="button-group float-right">
|
|
||||||
<button id="wait-button" class="btn btn-secondary mt-3 disabled">예약대기</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 결제창 ui -->
|
|
||||||
<div class="wrapper w-100">
|
|
||||||
<div class="max-w-540 w-100">
|
|
||||||
<div id="payment-method" class="w-100"></div>
|
|
||||||
<div id="agreement" class="w-100"></div>
|
|
||||||
<div class="btn-wrapper w-100">
|
|
||||||
<button id="reserve-button" class="btn primary w-100">예약하기</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/js/user-scripts.js"></script>
|
|
||||||
|
|
||||||
<script src="https://js.tosspayments.com/v1/payment-widget"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
|
|
||||||
<script src="/js/user-reservation.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ko">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Signup</title>
|
|
||||||
<!-- Bootstrap CSS -->
|
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
|
||||||
<a class="navbar-brand" href="/">
|
|
||||||
<img src="https://avatars.githubusercontent.com/u/141792611?s=200&v=4" alt="LOGO" style="height: 40px;">
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
|
|
||||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
|
||||||
<ul class="navbar-nav ml-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/reservation">Reservation</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/login">Login</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown"
|
|
||||||
aria-haspopup="true" aria-expanded="false">
|
|
||||||
<img class="profile-image" src="/image/default-profile.png" alt="Profile">
|
|
||||||
<span id="profile-name">Profile</span> <!-- 프로필 이름을 넣을 span 추가 -->
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
|
||||||
<a class="dropdown-item" href="/reservation-mine">My Reservation</a>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item" href="#" id="logout-btn">Logout</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="content-container" style="width: 400px;">
|
|
||||||
<h2 class="content-container-title">Signup</h2>
|
|
||||||
<form id="signup-form">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email">Email address</label>
|
|
||||||
<input type="email" class="form-control" id="email" placeholder="Enter email">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="password">Password</label>
|
|
||||||
<input type="password" class="form-control" id="password" placeholder="Enter password">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="name">Name</label>
|
|
||||||
<input type="name" class="form-control" id="name" placeholder="Enter name">
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-custom" onclick="register()">Register</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/js/user-scripts.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,133 +0,0 @@
|
|||||||
package roomescape.view
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
|
|
||||||
import org.springframework.test.web.servlet.MockMvc
|
|
||||||
import roomescape.util.RoomescapeApiTest
|
|
||||||
|
|
||||||
@WebMvcTest(controllers = [
|
|
||||||
AuthPageController::class,
|
|
||||||
AdminPageController::class,
|
|
||||||
ClientPageController::class
|
|
||||||
])
|
|
||||||
class PageControllerTest(
|
|
||||||
@Autowired private val mockMvc: MockMvc
|
|
||||||
) : RoomescapeApiTest() {
|
|
||||||
|
|
||||||
init {
|
|
||||||
listOf("/", "/login").forEach {
|
|
||||||
given("GET $it 요청은") {
|
|
||||||
`when`("로그인 및 권한 여부와 관계없이 성공한다.") {
|
|
||||||
then("비회원") {
|
|
||||||
doNotLogin()
|
|
||||||
|
|
||||||
runGetTest(
|
|
||||||
mockMvc = mockMvc,
|
|
||||||
endpoint = it,
|
|
||||||
) {
|
|
||||||
status { isOk() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
then("회원") {
|
|
||||||
loginAsUser()
|
|
||||||
|
|
||||||
runGetTest(
|
|
||||||
mockMvc = mockMvc,
|
|
||||||
endpoint = it,
|
|
||||||
) {
|
|
||||||
status { isOk() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
then("관리자") {
|
|
||||||
loginAsAdmin()
|
|
||||||
|
|
||||||
runGetTest(
|
|
||||||
mockMvc = mockMvc,
|
|
||||||
endpoint = it,
|
|
||||||
) {
|
|
||||||
status { isOk() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
listOf("/admin", "/admin/reservation", "/admin/time", "/admin/theme", "/admin/waiting").forEach {
|
|
||||||
given("GET $it 요청을") {
|
|
||||||
`when`("관리자가 보내면") {
|
|
||||||
loginAsAdmin()
|
|
||||||
|
|
||||||
then("성공한다.") {
|
|
||||||
runGetTest(
|
|
||||||
mockMvc = mockMvc,
|
|
||||||
endpoint = it,
|
|
||||||
) {
|
|
||||||
status { isOk() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
`when`("회원이 보내면") {
|
|
||||||
loginAsUser()
|
|
||||||
|
|
||||||
then("로그인 페이지로 이동한다.") {
|
|
||||||
runGetTest(
|
|
||||||
mockMvc = mockMvc,
|
|
||||||
endpoint = it,
|
|
||||||
) {
|
|
||||||
status { is3xxRedirection() }
|
|
||||||
header {
|
|
||||||
string("Location", "/login")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
listOf("/reservation", "/reservation-mine").forEach {
|
|
||||||
given("GET $it 요청을") {
|
|
||||||
`when`("로그인 된 회원이 보내면 성공한다.") {
|
|
||||||
then("회원") {
|
|
||||||
loginAsUser()
|
|
||||||
|
|
||||||
runGetTest(
|
|
||||||
mockMvc = mockMvc,
|
|
||||||
endpoint = it,
|
|
||||||
) {
|
|
||||||
status { isOk() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
then("관리자") {
|
|
||||||
loginAsAdmin()
|
|
||||||
|
|
||||||
runGetTest(
|
|
||||||
mockMvc = mockMvc,
|
|
||||||
endpoint = it,
|
|
||||||
) {
|
|
||||||
status { isOk() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
`when`("로그인 없이 보내면") {
|
|
||||||
then("로그인 페이지로 이동한다.") {
|
|
||||||
doNotLogin()
|
|
||||||
|
|
||||||
runGetTest(
|
|
||||||
mockMvc = mockMvc,
|
|
||||||
endpoint = it,
|
|
||||||
) {
|
|
||||||
status { is3xxRedirection() }
|
|
||||||
header {
|
|
||||||
string("Location", "/login")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user