코딩공작소
Todo 메인 페이지 기능 개발 및 서버 수정 본문
정말 오랜만에 자바스크립트를 개발했더니 머리가 아프다.
똘똘한 쥐피티의 도움도 많이 받았다.
<개발 및 수정 영역>
일단 기본적으로 포스트맨으로 테스트를 할 때는, Dto에 ReqeustBody어노테이션이 없어도 잘 통신이 되었는데, 검색을 좀 해보니 포스트맨과 서버 사이의 어느정도 자동으로 세팅되는 부분이 있어서 잘되었던 것 같다.
그래서 프론트에서 요청을 날렸을 때, 데이터를 잘 못읽어오는 부분이 있어서 @ReqeustBody 어노테이션을 붙여주니까 잘 통신되었다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Schedule Management</title>
<style>
/* 전체 테이블 스타일 */
.schedule-table {
width: 100%;
border-collapse: collapse;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
}
.schedule-table th, .schedule-table td {
border: 1px solid #e0e0e0;
padding: 12px;
text-align: center;
vertical-align: top;
font-size: 14px;
color: #333;
}
.date-cell {
font-weight: bold;
color: #007aff; /* iOS-style blue for date cells */
}
/* 버튼 및 텍스트 스타일 */
.add-button, .task-item {
font-size: 14px;
cursor: pointer;
color: #007aff;
background: none;
border: none;
text-decoration: none;
display: inline-block;
border-radius: 8px;
padding: 2px 6px;
margin: 2px;
transition: background-color 0.2s ease;
}
.add-button:hover, .task-item:hover {
background-color: #f0f0f5;
}
.task-item {
display: block; /* 세로로 쌓이도록 설정 */
margin-bottom: 5px; /* 각 태스크 간의 간격 조정 */
}
/* 체크박스 스타일 */
.task-check {
appearance: none;
background-color: #f0f0f5;
margin-left: 10px;
width: 18px;
height: 18px;
border: 2px solid #e0e0e0;
border-radius: 4px;
display: inline-block;
vertical-align: middle;
transition: background-color 0.3s, border-color 0.3s;
}
.task-check:checked {
background-color: #007aff;
border-color: #007aff;
}
.task-check:checked::before {
content: '✔';
color: #fff;
font-size: 14px;
position: absolute;
top: -1px;
left: 1px;
}
/* 팝업 오버레이 스타일 */
.popup-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.4);
justify-content: center;
align-items: center;
z-index: 1000;
}
/* 팝업 스타일 */
.popup {
background: #fff;
padding: 20px;
border-radius: 12px;
width: 90%;
max-width: 320px;
text-align: center;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.popup h3 {
font-size: 18px;
color: #333;
margin-bottom: 15px;
}
.popup input[type="text"] {
width: 100%;
padding: 10px;
border-radius: 8px;
border: 1px solid #e0e0e0;
margin: 10px 0;
font-size: 14px;
box-sizing: border-box;
}
.popup button {
margin: 5px;
padding: 10px 15px;
font-size: 14px;
color: #007aff;
background: #f0f0f5;
border: none;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.2s ease;
}
.popup button:hover {
background-color: #e0e0e5;
}
.popup button#saveButton {
color: #fff;
background-color: #007aff;
}
.popup button#saveButton:hover {
background-color: #005bb5;
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
function loadTasksWithDateComparison(tasks) {
const dateHeaders = Array.from(document.querySelectorAll('.date-header'));
const dateMap = dateHeaders.reduce((map, header, index) => {
const dateText = header.textContent.split(" ")[0]; // "MM/DD" 형식 추출
map[dateText] = index;
return map;
}, {});
const taskBody = document.getElementById('task-body');
taskBody.innerHTML = ''; // 기존 내용을 지우고 새로 채움
// 첫 번째 행 생성 및 7개의 셀 추가
let firstRow = document.createElement('tr');
for (let i = 0; i < 7; i++) {
firstRow.insertCell();
}
// 서버에서 받아온 데이터로 각 날짜에 맞게 태스크를 배치
tasks.forEach(task => {
const taskDate = formatDate(new Date(task.createdDate)); // task.time이 "MM/DD" 형식이라 가정
if (dateMap[taskDate] !== undefined) {
const cellIndex = dateMap[taskDate]; // 해당 날짜에 해당하는 셀의 인덱스
const cell = firstRow.cells[cellIndex]; // 첫 번째 행의 셀을 가져옴
// task-item 생성
const taskDiv = document.createElement('div');
taskDiv.className = 'task-item';
taskDiv.textContent = task.content;
taskDiv.setAttribute('data-task-id', task.id); // 히든 속성으로 id 저장
// 체크박스 추가
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'task-check';
checkbox.checked = task.completeYn == "Y" ? true : false;
checkbox.onclick = function (event) {
event.stopPropagation();
checkboxEvent(task.id, checkbox, task.content);
};
taskDiv.setAttribute('onclick', `openEditPopup(event, '${task.content}', '${task.id}')`);
// 구성한 요소 추가
taskDiv.appendChild(checkbox);
cell.appendChild(taskDiv);
}
});
// 수정된 첫 번째 행을 taskBody에 추가
taskBody.appendChild(firstRow);
}
function selectTodo() {
$.ajax({
url: '/todo/selectAll',
method: 'GET',
success: function (response) {
loadTasksWithDateComparison(response);
},
error: function (xhr, status, error) {
console.error('Failed to load tasks:', error);
}
});
}
function init(){
selectTodo();
}
function formatDate(date) {
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${month}/${day}`;
}
function loadDates() {
const today = new Date();
const dates = [];
for (let i = -3; i <= 3; i++) {
const currentDate = new Date(today);
currentDate.setDate(today.getDate() + i);
dates.push(formatDate(currentDate));
}
const dateHeaders = document.querySelectorAll('.date-header');
dateHeaders.forEach((header, index) => {
header.innerHTML = `${dates[index]} <button class="add-button" onclick="openAddPopup('${dates[index]}')">(+)</button>`;
});
init();
}
function openAddPopup(date) {
document.getElementById('popupTitle').innerText = `Add Task for ${date}`;
document.getElementById('popup').style.display = 'flex';
document.getElementById('popup').setAttribute('data-action', 'add');
document.getElementById('popup').setAttribute('data-date', date); // 팝업에 id 저장
$("#deleteButton").hide();
}
function openEditPopup(event, task, taskId) {
event.stopPropagation(); // Prevent checkbox click from triggering the popup
document.getElementById('popupTitle').innerText = `Edit Task: ${task}`;
document.getElementById('popup').style.display = 'flex';
document.getElementById('popup').setAttribute('data-task-id', taskId); // 팝업에 id 저장
document.getElementById('popup').setAttribute('data-action', 'edit');
$("#deleteButton").show();
}
function closePopup() {
document.getElementById('popup').style.display = 'none';
document.getElementById('taskInput').value = '';
}
function addTodo(taskInput){
const data = {
"content" : taskInput,
"completeYn" : "N",
"createdDate" : document.getElementById('popup').getAttribute('data-date')
};
$.ajax({
url: '/todo/addTodo',
method: 'POST',
contentType : "application/json",
data : JSON.stringify(data),
success: function (response) {
alert("성공적으로 등록하였습니다.");
location.reload(true);
},
error: function (xhr, status, error) {
console.error('Failed to load tasks:', error);
}
});
}
function updateTodo(taskInput){
const data = {
"todoId" : document.getElementById('popup').getAttribute('data-task-id'),
"content" : taskInput,
"completeYn" : document.getElementById('popup').getAttribute('data-checked') == true ? "Y" : "N",
};
$.ajax({
url: '/todo/editTodo',
method: 'PUT',
contentType : "application/json",
data : JSON.stringify(data),
success: function (response) {
location.reload(true);
},
error: function (xhr, status, error) {
console.error('Failed to load tasks:', error);
}
});
}
function checkboxEvent(id, checkbox, content){
const data = {
"todoId" : id,
"content" : content,
"completeYn" : checkbox.checked == true ? "Y" : "N"
};
$.ajax({
url: '/todo/editTodo',
method: 'PUT',
contentType : "application/json",
data : JSON.stringify(data),
success: function (response) {
},
error: function (xhr, status, error) {
console.error('Failed to load tasks:', error);
}
});
}
function deleteTodo(){
const id = document.getElementById('popup').getAttribute('data-task-id');
$.ajax({
url: '/todo/deleteTodo/' + id,
method: 'DELETE',
contentType : "application/json",
success: function (response) {
location.reload(true);
},
error: function (xhr, status, error) {
console.error('Failed to load tasks:', error);
}
});
}
function handlePopupAction() {
const action = document.getElementById('popup').getAttribute('data-action');
const taskInput = document.getElementById('taskInput').value;
if(taskInput == null || taskInput == "") {
alert("메세지를 입력하세요.");
return;
}
if (action === 'add') {
addTodo(taskInput);
} else if (action === 'edit') {
updateTodo(taskInput);
}
closePopup();
}
function deleteTask() {
closePopup();
deleteTodo();
}
window.onload = loadDates;
</script>
</head>
<body>
<h2>재미로 만든 일정관리표(by.TH)</h2>
<table class="schedule-table">
<thead>
<tr>
<th class="date-cell date-header"></th>
<th class="date-cell date-header"></th>
<th class="date-cell date-header"></th>
<th class="date-cell date-header"></th>
<th class="date-cell date-header"></th>
<th class="date-cell date-header"></th>
<th class="date-cell date-header"></th>
</tr>
</thead>
<tbody id="task-body">
</tbody>
</table>
<!-- Popup for Adding/Editing Task -->
<div id="popup" class="popup-overlay" onclick="event.target === this && closePopup()">
<div class="popup">
<h3 id="popupTitle">Popup Title</h3>
<input type="text" id="taskInput" placeholder="Enter task name">
<div id="popupButtons">
<button onclick="handlePopupAction()" id="saveButton">Save</button>
<button onclick="deleteTask()" id="deleteButton">Delete</button>
<button onclick="closePopup()">Cancel</button>
</div>
</div>
</div>
</body>
</html>
프론트엔드 소스이다.
목적은 프론트엔드 개발 공부가 아니기 때문에, 일단 한 파일에 다 몰아서 주먹구구식 개발을 했다.
기본적으로 페이지가 로딩될 때, 데이터를 전체적으로 읽어와 해당 날짜에 맞는 투두들을 템플릿 처럼 뿌려준다
그리고, 그에 대한 CRUD기능을 추가하였다.
쥐피티에서 뿌리는 부분을 맡겼는데, 아이디어를 보고 감탄..
날짜별 데이터를 뿌려야되는 부분을 요소들에서 날짜를 가져와서 Map으로 변환시켜주고,
결과 리스트를 돌면서 해당 맵의 요소들에 맞는 투두 리스트들을 그려주었다.
아이폰 스러운 디자인으로 css 디자인도 부탁했더니 잘 그려주었다 ㅎㅎ
여기까지 기본적인 아주 간단한 Todo 리스트의 최소한의 기능을 가진 페이지를 만들었다.
EC2를 만들고 데이터 베이스를 만들어서 Mysql를 설치하고 인스턴스들의 인바운드/아웃바운드 그룹들을 설정하여 통신을 했다.
그리고 JPA와 테스트코드들을 이용하여 기본적은 서버단을 만들고 자바스크립트로 Ajax통신을 통해 기능들을 만들어주었다.
'어플리케이션개발 > 토이프로젝트(취미)' 카테고리의 다른 글
로그인 및 회원가입 페이지 & 스프링 시큐리티 개발 최종 정리 (0) | 2024.12.04 |
---|---|
회원 도메인 추가/회원가입 및 Todo 도메인과 연결 (0) | 2024.11.19 |
REST Api 컨트롤러 개발 (0) | 2024.10.31 |
토이프로젝트 (Made by.GPT) (1) | 2024.10.30 |
스프링부트 Service단에 대한 테스트코드 개발 (0) | 2024.10.24 |