국비/Project-3 채용박람회
2023.07.14 117일차 Team Project - 2 채용박람회
춘핑이
2023. 7. 29. 15:57
117일차
1. 채용공고 상세페이지
1.1 view
다음의 view를 기준으로 설정한다.
입사지원 스크랩 담당자에게 쪽지보내기를 수행할 수 있다.
<div class="container mt-5">
<div class="row">
<div class="col-md-6 offset-md-3">
<img src="/img/3.jpg" class="img-fluid rounded-circle mx-auto d-block" alt="회사 로고">
<h2 class="text-center my-4">회사이름</h2>
<h4 class="my-4">공고명 대충 길게</h4>
<h5 class="my-3">근무 조건</h5>
<ul>
<div id="company-name" class="text-center my-4">
<h2>회사이름</h2>
<a href="">회사 정보 상세 보기></a>
</div>
<hr>
<div style="text-align: center">
<h2 id="title" class="my-4">공고명 대충 길게</h2>
</div>
<h4 class="my-3"><i class="fa-solid fa-list"></i> 근무 조건</h4>
<ul id="work-condition-ul">
<li>근무지: 대충 주소</li>
<li>근무형태: 정규직</li>
<li>급여: 100원</li>
<li>연봉: 100원</li>
</ul>
<h4 class="my-4">주요 산업</h4>
<input style="resize: none" class="form-control-plaintext fs-5" cols="30" rows="5" value="대충 it" readonly/>
<h4 class="my-3"><i class="fa-solid fa-handshake"></i> 필요 인원</h4>
<input style="resize: none" id="hiring_count" class="form-control-plaintext fs-5" value="00명" readonly/>
<h4 class="my-4">이런 사람들이 필요해요!</h4>
<input style="resize: none" class="form-control-plaintext" cols="30" rows="5" readonly/>
<h4 class="my-3"><i class="fa-solid fa-industry"></i> 우리 회사의 업종입니다</h4>
<input id="industry-id" class="form-control-plaintext fs-5" value="대충 it" readonly/>
<h4 class="my-4">우대 사항</h4>
<textarea style="resize: none" class="form-control-plaintext" cols="30" rows="5" readonly>${post.preference}
<h4 class="my-4"><i class="fa-solid fa-user-check"></i> 필요조건입니다</h4>
<input id="level" class="form-control-plaintext" value="대졸이상 경력" readonly/>
<textarea id="requirements" style="resize: none" class="form-control-plaintext" cols="30" rows="2" readonly>
</textarea>
<h4 class="my-4">기타 사항</h4>
<textarea style="resize: none" class="form-control-plaintext" cols="30" rows="5" readonly>${post.etc}
<h4 class="my-4"><i class="fa-solid fa-user-plus"></i> 우대사항입니다</h4>
<textarea id="preferences" style="resize: none; overflow: hidden" class="form-control-plaintext" cols="30"
rows="2" readonly>
</textarea>
<div style="text-align: right;">
<button class="btn btn-outline-success">수정</button>
<button class="btn btn-outline-danger" data-bs-toggle="modal" data-bs-target="#deletePosting">삭제
</button>
<button class="btn btn-outline-secondary"
onclick="location.href='/member/company/posting/list?memberId=${post.memberId}'">목록
<h4 class="my-4"><i class="fa-solid fa-hand-holding-heart"></i> 이런 복지가 있습니다</h4>
<textarea id="benefits" style="resize: none" class="form-control-plaintext ta" cols="30" rows="2" readonly>
</textarea>
<h4 class="my-3"><i class="fa-regular fa-calendar-days"></i> 모집기간</h4>
<input id="period" style="resize: none" class="form-control-plaintext fs-5" value="2023.07.14 ~ 2023.07.89"
readonly/>
<div id="etc">
<h4 class="my-4">기타 사항</h4>
<textarea style="resize: none" class="form-control-plaintext" cols="30" readonly>
</textarea>
</div>
<div style="text-align: center;">
<input type="hidden" id="spare_count">
<input type="hidden" id="application_count">
<button class="btn btn-outline-success"><i class="fa-regular fa-paper-plane"></i> 입사지원하기</button>
<button id="wish-btn"></button>
<button id="note-btn" class="btn btn-outline-primary"><i class="fa-regular fa-envelope"></i> 쪽지로 문의하기
</button>
<a href="/user/posting/list" class="btn btn-outline-secondary">목록으로가기</a>
</div>
<br>
</div>
</div>
</div>1.2 포스팅 서비스와 스크랩 서비스
스크랩이 되었으면 스크랩되어있는지를 표기해주어야한다.
@Override
public Map<String, Object> getDetail(Integer postingId, Authentication authentication) {
Posting posting = postingMapper.getPostViewDetailByPostingId(postingId);
List<Industry> industryList = industryService.getIndustryList();
String senderId = null;
posting.setScraped(false);
// 해당 계정이 좋아요눌럿는지와 로그인되어잇는지 체크
if (authentication == null) {
senderId = "notLogin";
} else {
Scrap scrap = scrapMapper.scrapCheck(postingId, authentication.getName());
if (scrap != null) {
posting.setScraped(true);
}
senderId = authentication.getName();
}
return Map.of("posting", posting, "industryList", industryList, "senderId", senderId);
}스크랩은 삭제쿼리를 먼저 실행한 후 좋아요를 업데이트 하는 방식으로 한다.
그 이유는 현재 눌러져있다면 취소를 하는 것이다.
만약 삭제처리후 값이 없다면 좋아요를 실행시켜서 넣게 된다.
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> scrap(Integer postingId, Authentication authentication) {
// 삭제 쿼리 먼저
Integer deleteCnt = scrapMapper.delete(postingId, authentication.getName());
Integer insertCnt = 0;
// 삭제 되지 않았다면 좋아요 없는 상태이니 추가
if (deleteCnt != 1){
// 스크랩
insertCnt = scrapMapper.insert(postingId, authentication.getName());
}
return Map.of("scraped", insertCnt == 1);
}1.3 js
컨트롤러에서 서비스 처리 후 응답을 돌려주는 것을 ajax로 view에 매핑해주었다.
쪽지이벤트는 로그인되어있지않다면 로그인해달라는 토스트 메시지를 보낸다.
로그인이 되어 있다면 해당 링크로 보내는사람과 받는사람을 넣어서 링크를 요청했다.
이 요청은 내가 만든 코드가 아니기에 팀원의 코드를 그대로 가져다 쓴것이다.
좋아요 상태버튼을 좋아요 상태에 따라 찜취소 버튼(이미 눌린상태) 찜버튼(안눌린상태)로 보여준다.
좋아요도 ajax를통해 요청을하게 되고 비로그인시 로그인을 해달라는 요청을 하게 된다.
function detailView() {
const url = window.location.href;
const postingId = url.substring(url.lastIndexOf("/") + 1);
fetch(`/api/user/posting/${postingId}`)
.then(response => response.json())
.then(data => {
const posting = data.posting;
const companyName = document.querySelector("#company-name");
const title = document.querySelector("#title");
const workConditionUl = document.querySelector("#work-condition-ul");
const hiringCount = document.querySelector("#hiring_count");
const industry = document.querySelector("#industry-id");
const level = document.querySelector("#level");
const requirements = document.querySelector("#requirements");
const preferences = document.querySelector("#preferences");
const benefits = document.querySelector("#benefits");
const period = document.querySelector("#period");
const etc = document.querySelector("#etc");
const applicationCount = document.querySelector("#application_count");
const spareCount = document.querySelector("#spare_count");
spareCount.value = posting.spareCount;
applicationCount.value = posting.applicationCount;
const industryId = posting.industryId - 1;
const industryName = data.industryList[industryId].industryName;
const h2Element = companyName.querySelector("h2");
const aElement = companyName.querySelector("a");
h2Element.innerHTML = posting.companyName;
aElement.href = `/company/${posting.companyId}`;
title.innerHTML = posting.title;
workConditionUl.innerHTML = "";
const conditionHtml = `
<li>근무지: ${posting.address}</li>
<li>근무형태: ${posting.employmentType}</li>
<li>연봉: ${posting.salary}원</li>
`;
workConditionUl.innerHTML = conditionHtml;
hiringCount.value = posting.hiringCount + '명';
industry.value = industryName;
level.value = posting.experienceLevel + " " + posting.educationLevel;
requirements.innerHTML = posting.requirement;
resizeTextarea(requirements);
preferences.innerHTML = posting.preference;
resizeTextarea(preferences);
benefits.innerHTML = posting.benefit;
resizeTextarea(benefits);
period.value = `${posting.startDate} ~ ${posting.endDate}`;
const textAreaElement = etc.querySelector("textarea");
if (posting.etc != null) {
textAreaElement.innerHTML = posting.etc;
resizeTextarea(etc);
} else {
etc.classList = "d-none";
}
const senderId = data.senderId;
const recipientId = posting.memberId;
//쪽지 처리
const noteBtn = document.querySelector("#note-btn");
if (senderId === "notLogin") {
noteBtn.addEventListener("click", function () {
toastBody.innerHTML = "로그인 후 이용해주세요!";
toastBootstrap.show();
});
} else {
// 쪽지보내기 이벤트 넣기
noteBtn.addEventListener("click", function () {
const Url = `/note/write?senderId=${senderId}&recipientId=${recipientId}`
window.open(Url, '_blank', 'width=600,height=400');
});
}
// 좋아요 상태 표시
const wishBtn = document.querySelector("#wish-btn");
if (posting.scraped) {
wishBtn.innerHTML = "<i class=\"fa-regular fa-star\"></i> 공고찜취소";
wishBtn.classList = "btn btn-outline-danger";
} else {
wishBtn.innerHTML = "<i class=\"fa-regular fa-star\"></i> 공고찜하기";
wishBtn.classList = "btn btn-outline-warning";
}
wishBtn.addEventListener("click", wish);
})
.catch(error => {
console.error("Error:", error);
});}
function wish() {
const url = window.location.href;
const postingId = url.substring(url.lastIndexOf("/") + 1);
fetch(`/scrap/${postingId}`, {
method: "POST"
})
.then(response => {
if (response.status === 403) {
toastBody.innerHTML = "로그인 후 이용해주세요!";
toastBootstrap.show();
}
return response.json();
})
.then(data => {
const scraped = data.scraped;
const wishBtn = document.querySelector("#wish-btn");
if (scraped === true) {
wishBtn.innerHTML = "<i class=\"fa-regular fa-star\"></i> 공고찜취소";
wishBtn.classList = "btn btn-outline-danger";
} else {
wishBtn.innerHTML = "<i class=\"fa-regular fa-star\"></i> 공고찜하기";
wishBtn.classList = "btn btn-outline-warning";
}
})
.catch(error => {
console.error("Error:", error);
});
}2023.07.29
프로젝트들을 하면서 아쉬운점이 있다.
best case만 생각하고 코드를 작성했기 때문에 오류처리같은것이 제대로 되어잇지않다.
현재 작성하고 있는 코드들도 무결성 검사를 프론트에서 하는 것이 많고 백에서 제대로 되는게 별로없다.
콘솔창을 통해서 악한사람이라면 데이터를 조작할수도 있음을 알게되는 것 같다.
좀 더 신경써서 세세한 부분을 조절할 수 있었다면 좋았을 것 같다.