D+68:: SHOP_상품상태 및 재고수량 변경 기능 구현/MAP/LIST 데이터 HTML 사용하는 방법

Am.Vinch·2022년 10월 5일
0

20221004_tue

지난시간 내용 정리

  • 상품 재고수량 변경 기능 구현
  • 상품 판매상태 변경 기능 구현

재고수량 변경 및 상품 판매상태 변경 기능 구현

  • item_manage.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout" 
	layout:decorate="~{layout/admin_layout}"
	xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
	
<div layout:fragment="content">
	 <div class="row">
	 	<div class="col">
			<h1>상품관리페이지</h1>
	 		<div class="row"><!-- 좌우측화면 담고있는 화면 -->
				<!-- 검색 -->
				<div class="col-12 mb-5">
				<div>
				<table class="table table-striped table-hover">
				  <tr>
				  	<td> 카테고리 </td>
				  	<td colspan="2"> 
					  	<select >
					  		<option th:each="cateList : ${cateListAll}" th:text="${cateList.cateName}"></option>
					  	</select> 
				  	</td>
				  	<td>상품명</td>
				  	<td><input type="text"> </td>
				  	<td>재고</td>
				  	<td><input type="number" > </td>
				  </tr>
				  
				  <tr>
				  	<td>등록일</td>
				  	<td><input type="date"></td> 
				  	<td><input type="date"></td>
				  	<td>상태</td>
				  	<td colspan="3">
					  	<input type="radio" value="전체" th:text="전체" checked="checked"> 
					  	<input type="radio" value="판매중" th:text="판매중"> 
					  	<input type="radio" value="매진" th:text="매진"> 
				  	</td>
				  </tr>
				  
				</table>
				</div>
				<div align="center">
				  	<button class="btn btn-outline-secondary" type="submit">검색</button> 
				</div>
				</div>	 		
				
				<!-- 목록테이블 제목줄 -->
				<div class="col-12 mb-5">
				<table class="table table-striped table-hover">
				  <colgroup>
				  	<col width="*%">
				  	<col width="10%">
				  	<col width="20%">
				  	<col width="10%">
				  	<col width="20%">
				  	<col width="20%">
				  	<col width="20%">
				  </colgroup>
				  <thead>
					  <tr>
					  	<th>No.</th>
					  	<th>카테고리</th>
					  	<th>상품명</th>
					  	<th>가격</th>
					  	<th>재고</th>
					  	<th>등록일</th>
					  	<th>상태</th>
					  </tr>
				  </thead>
				  <tbody>
					<th:block th:if="${#lists.size(regItemList) == 0}">
						<tr>
							<td colspan="7"> 조회된 목록이 없습니다.</td>
						</tr>
					</th:block>
					<th:block th:unless="${#lists.size(regItemList) == 0}">
						<th:block th:each="regItemInfo , status : ${regItemList}" >
							<tr>
								<td th:text="${status.count}"></td>
								<td th:text="${regItemInfo.cateName}"></td>
								<td th:text="${regItemInfo.itemName}"></td>
								<td th:text="${regItemInfo.itemPrice}"></td>
								<td> 
									<div class="input-group mb-3">
  										<input id="itemStocks" type="number" th:value="${regItemInfo.itemStock}"  class="form-control stockInput" 
  												aria-label="Recipient's username" aria-describedby="button-addon2">
									 	 <!-- 타임리프로 데이터 던질때 주의할 점: 대괄호 두개 달러 중괄호 사용하기 -->
									 	 <!--  단, 데이터를 던질때 value값 itemstock을 던지면 변경한 숫자가 넘어가는것이아니라 
									 	       원래 있던 변경 재고값이 넘어가는 것이기때문에 itemcode값을 던져야한다.-->
									 	 <!--  this 를 넘기면 내가 뭔가 액션을 취한 (변경버튼클릭했을 때의 그 값)이 넘어간다.-->
									 	 <button class="btn btn-outline-secondary" type="submit" id="button-addon2" 
									 	 			th:onclick="changeStock([[${regItemInfo.itemCode}]] ,this);" >변경</button>
									</div>
								</td>
								<td th:text="${regItemInfo.regDate}"></td>
								<!-- 라디오값-->
								<!-- 변수와 문자열을 동시에 사용하기위해서 -->
								<!-- 네임값에 버티컬바 두개를 넣으면 편히 넣어 데이터넘길수있다. -->
								
								<td id="itemStatusId">
									<div class="form-check form-check-inline">
				                       <input class="form-check-input " type="radio" th:name="|itemStatus_${status.count}|" 
				                       	id="onSale" value="ON_SALE" th:checked="${regItemInfo.itemStatus eq '판매중'}"
				                       	th:onclick="changeItemStatus([[${regItemInfo.itemCode}]], 'ON_SALE');" >       
				                       <label class="form-check-label" for="inlineRadio1">판매중</label>
				                     </div>
				                     <div class="form-check form-check-inline">
				                      <input class="form-check-input " type="radio" th:name="|itemStatus_${status.count}|" 
				                      	 id="soldOut" value="SOLD_OUT" th:checked="${regItemInfo.itemStatus eq '매진'}"
        	 				             th:onclick="changeItemStatus([[${regItemInfo.itemCode}]],'SOLD_OUT');" >       
				                       <label class="form-check-label" for="inlineRadio2">매진</label>
				                     </div>
								</td>
							</tr>
						</th:block>
					</th:block>
					</tbody>
				</table>
				</div>	 	
	 		</div>

	 	</div>
	 </div>
	 
	 <!-- 수량 변경 후 실행되는 modal창 -->
	<!-- Modal -->
	<div class="modal fade" id="updateStockModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
	  <div class="modal-dialog">
	    <div class="modal-content">
	      <div class="modal-body">
	       <div class="row mb-3">
	       		<div class="col">
			        상품수량을 변경했습니다.
	       		</div>
	       </div>
	       <div class="row">
	       		<!-- text-end: 글자 우측정렬 btn-sm: 작은 버튼-->
	       		<div class="col text-end">
			         <button type="button" class="btn btn-primary  btn-sm" data-bs-dismiss="modal" 
			         		style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: 1.5rem; --bs-btn-font-size: 1rem;" >확인</button>
	       		</div>
	       </div>
	      </div>
	    </div>
	  </div>
	</div>
	<!-- modal -->
	
	 <!-- 상품상태 변경 후 실행되는 modal창 -->
	<!-- Modal -->
	<div class="modal fade" id="updateStatusModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
	  <div class="modal-dialog">
	    <div class="modal-content">
	      <div class="modal-body">
	       <div class="row mb-3">
	       		<div class="col">
			        상품 판매상태를 변경했습니다.
	       		</div>
	       </div>
	       <div class="row">
	       		<div class="col text-end">
			         <button type="button" class="btn btn-primary  btn-sm" data-bs-dismiss="modal" 
			         		style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: 1.5rem; --bs-btn-font-size: 1rem;" >확인</button>
	       		</div>
	       </div>
	      </div>
	    </div>
	  </div>
	</div>
	<!-- modal -->
	 
<!-- 반드시 해당되는 div 태그 안에 있어야 실행이 된다. -->	
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript" th:src="@{/js/layout/item_manage.js}"></script>	 
</div>

</html>
  • itme_manage.js
    • 태그선택 요소
  • this라는 액션을 취할 때 = 변경버튼클릭할 때 태그가 어딨는지 찾아가야한다.
    - 형제의 자식태그 밸류값을 빼온다
    -부모태그 찾아갈때 : parentElemnet
    - 이전형제태그 찾아갈때 : previousElemnetSibling
    - 다음형제 노드 : nextElemnetSibling
    - 감싸고 있는 태그 중 가장 가까운 상위태그 : closest()
    - 자식태그(모든) : children
    - 자식이 여러명있을 때 배열로 접근하여 자식태그를 선택해준다
  • 재고 수량 value값 선택하여 갖고오기
    : 태그를 찾아가서 변경될 수량 input태그 밸류값을 가져온다.

  • 데이터 하나만 들고올 때
const itemStock = selectedTag.parentElemnet.previousElemnetSibling.children[0].vlaue;
  • 데이터 여러개 들고올때
    : 단, 아래처럼 클래스로 불러오게되면 for문을 도는 반복문이기때문에 중복이되어 데이터를 들고오지않는다.
    왜냐면 내가 실제로 변경할 인풋태그 값 하나만 불러와야하기때문이다.
  • 잘못된 방법
    const itemStock = document.querySelectorAll('.stockInput');
  • 올바른 방법
    : 선택한 태그를 둘러싸고 있는! 감싸고있는!(감싸지않으면 x) 가장 가까운 td태그 선택
    <방법 1>
    const itemStock1 = selectedTag.parentElemnet.children[0].vlaue;
    <방법 2>
    const itemStock = selectedTag.closest('td').querySelector('.stockInput').vlaue;

//-------------------상품(판매)여부 라디오 버튼 클릭시 진행되는 함수---------------------------------//
function changeItemStatus(itemCode, status){
	
      $.ajax({
         url: '/admin/changeItemStatus', //요청경로
         type: 'post',
         data: {'itemStatus':status,'itemCode':itemCode}, //필요한 데이터
         success: function(result) {
			const modal = new bootstrap.Modal('#updateStatusModal');
			modal.show();  
         },
         error: function() {
            alert('실패');
         }
      });
}



//--------------------------재고 수량변경 버튼 클릭시 진행되는 함수---------------------------------//
function changeStock(itemCode, selectedTag){
	 const itemStock = selectedTag.closest('td').querySelector('#itemStocks').value;
    
      //1) ajax start
      $.ajax({
         url: '/admin/changeStock', //요청경로
         type: 'post',
         data: {'itemCode':itemCode ,'itemStock':itemStock}, //필요한 데이터
         success: function(result) {
		// -- alert창 띄우는 ver.
        // alert('수량을 변경했습니다');
        // -- modal창 띄우는 ver.
        // 버튼을 클릭했을 때가 아니라 원했을 때 띄우기
	        const modal = new bootstrap.Modal('#updateStockModal');
			modal.show();        
        
         },
         error: function() {
            alert('실패');
         }
      });
      //1) ajax end
}

Controller

  • adminController
package Kh.study.shop.admin.controller;

import java.util.List;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import Kh.study.shop.admin.service.AdminService;
import Kh.study.shop.item.service.ItemService;
import Kh.study.shop.item.vo.CategoryVO;
import Kh.study.shop.item.vo.ItemVO;
import Kh.study.shop.member.service.MemberService;
import Kh.study.shop.member.vo.MemberVO;

@Controller
@RequestMapping("/admin")
public class AdminController {
////////////////////////////////////////////////////
	@Resource(name = "adminService")
	private AdminService adminService;
	@Resource(name = "memberService")
	private MemberService memberService;
	@Resource(name = "itemService")
	private ItemService itemService;
/////////////////////////////////////////////////////
	
	//관리자_첫화면 :상품등록 및 상세페이지 동시
	@GetMapping("/regCate")
	public String admin(Model model, ItemVO itemVO) {
		//전체 카테고리  목록조회(좌측화면에만 데이터 뿌리기)
		model.addAttribute("cateListAll",adminService.cateListAll());
		
		//'사용' 중 카테고리 목록 조회(우측화면 상품등록시 적용되도록)
		model.addAttribute("cateListUse",adminService.cateListUse());

		return "content/admin/reg_item";
	}
	
	//카테고리 등록
	@PostMapping("/regCate")
	public String regCate(CategoryVO categoryVO) {
		adminService.regCate(categoryVO);
		// 카테고리 등록 후, 다시 첫화면 페이지로 이동
		return "redirect:/admin/regCate";
	}
	
	//카테고리상태 변경(ajax실행)
	//ajax사용하는 이유! 페이지이동없이하려고!!
	// -> 그래서 페이지이동 리턴값이 없다!
	@ResponseBody
	@PostMapping("/changeStatus")
	public void changeStatus(CategoryVO categoryVO) {
		//상태변경하기
		adminService.changeStatus(categoryVO);
	}
	
	// 사용중 카테고리 목록 조회(ajax사용)
	@ResponseBody
	@PostMapping("/selectCategoryListInUseAjax")
	public List<CategoryVO>  selectCategoryListInUseAjax() {
		//사용중 카테고리 목록 재조회
		List<CategoryVO> cateList = adminService.cateListUse();
		return cateList;
	}
	
	//실제 상품등록
	@PostMapping("/regItem")
	public String reg(ItemVO itemVO) {
		adminService.regItem(itemVO);
		return "redirect:/admin/regCate";//관리자 첫화면
	}
	
	
	//회원권한설정클릭시 첫화면
	@GetMapping("/setMemberRole")
	public String setMemberRoleProccess(Model model,MemberVO memberVO) {
		model.addAttribute("memberList",memberService.selectMemberList());
		return "content/admin/set_member_role" ;
	}
	
	
	 // (ajax)회원 상세정보조회
	@ResponseBody
	@PostMapping("/showDetailmemberInfo") 
	public MemberVO showDetailmemberInfo(String memberId) { 
		MemberVO memberInfo = memberService.selectMemberDetail(memberId);
		return memberInfo;
	}
	
	//상세조회 후 수정하기
	@PostMapping("/updateMemberRole")
	public String updateMemberRole(MemberVO memberVO) {
		// 수정쿼리문
		return "redirect:/admin/setMemberRole";
	}
	
	//상품관리 클릭시
	@GetMapping("/itemManage")
	public String itemManage(Model model, ItemVO itemVO) {
		// 등록된 상품목록조회
		model.addAttribute("regItemList", itemService.selectRegItemList());
		// 카테고리 목록조회
		model.addAttribute("cateListAll",adminService.cateListAll());
		return "content/admin/item_manage";
	}
	 
	//(ajax) 수량 변경
	@ResponseBody
	@PostMapping("/changeStock")
	public void changeStock(ItemVO itemVO) {
		itemService.updateStock(itemVO);
	}
	//(ajax) 상품판매여부 변경
	@ResponseBody
	@PostMapping("/changeItemStatus")
	public void changeItemStatus(ItemVO itemVO) {
		itemService.updateStautus(itemVO);
	}
	
	
}
  • mapper
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--해당 파일에 모든 쿼리문을 작성 -->
<mapper namespace="itemMapper">
	<!-- 패키지명 클래스명 -->
 	<resultMap type="Kh.study.shop.item.vo.ItemVO" id="item">
		<id column="ITEM_CODE" 	property="itemCode"/>
		<result column="ITEM_NAME" property="itemName"/>
		<result column="ITEM_PRICE" property="itemPrice"/>
		<result column="ITEM_COMMENT" property="itemComment"/>
		<result column="REG_DATE" property="regDate"/>
		<result column="ITEM_STOCK" property="itemStock"/>
		<result column="ITEM_STATUS" property="itemStatus"/>
		<result column="CATE_CODE" 	property="cateCode"/>

		<result column="CATE_NAME" property="cateName"/>
		<result column="CATE_STATUS" property="cateStatus"/>
	</resultMap>
	
<!-- 상품목록조회 -->
<select id="selectItemList" resultMap="item">
   SELECT 
  	 ITEM_CODE
	,ITEM_NAME
	,ITEM_PRICE
	,ITEM_COMMENT
	,REG_DATE
	,ITEM_STOCK
	,CATE_CODE
   FROM SHOP_ITEM
   ORDER by ITEM_CODE
</select> 

<!-- 상품관리 페이지 이동시  -->
<!-- 상품+카테고리 함께 목록조회 -->

<!-- 조건검색기능 -->
<!-- 조건절 AND로 계속 연결된다.-->
<!--  대소문자 구분없이   -->
<!-- <= 사용시, 부등호를 태그로 인식하기때문에 -->
<!-- less than: $lt; greater than : $gt; -->

<!-- 날짜비교 중요한사항! -->
<!-- ! 그대로 컬러명사용하는 것이아니라 형변환(TO_CHAR)하여 진행해야 비교가 온전히 가능하다-->
<!-- 모든 값은 if문으로 넣어 사용해야한다.  -->
<select id="selectRegItemList" resultMap="item">
	SELECT 
	ITEM_CODE
		,ITEM_NAME
		,ITEM_PRICE
		,to_char(REG_DATE, 'YYYY-MM-DD') REG_DATE
		,ITEM_STOCK
		,SHOP_ITEM.CATE_CODE
	    ,CATE_NAME
	    ,DECODE(ITEM_STATUS,'ON_SALE','판매중','매진')  ITEM_STATUS
	FROM  SHOP_ITEM,ITEM_CATECGORY
	WHERE SHOP_ITEM.CATE_CODE = ITEM_CATECGORY.CATE_CODE
	<!-- <if test="">
	AND SHOP_ITEM.CATE_CODE = #{cateCode} 
	</if>
	<if test="">
	AND UPPER(ITEM_NAME) LIKE UPPER('%#{itemName}%')
	</if>
	<if test="">
	AND ITEM_STOCK &lt;= #{itemStock}
	</if>
	<if test="">
	AND TO_CHAR(REG_DATE,'YYYY-DD-MM') &gt;= '#{regDate}' 
	</if>
	<if test="">
	AND TO_CHAR(REG_DATE,'YYYY-DD-MM') &lt;= '#{regDate}' 
	</if>
	<if test="">
	AND ITEM_STATUS = 'ON_SALE'
	</if> -->
	
</select>
<!-- 재고 수량 변경 -->
  <update id="updateStock" >
	UPDATE SHOP_ITEM 
	SET 
	ITEM_STOCK = #{itemStock}
	WHERE ITEM_CODE = #{itemCode}
</update>  

<!-- 상품상태 여부 변경 -->
<update id="updateStautus" >
	UPDATE SHOP_ITEM 
	SET
	ITEM_STATUS = #{itemStatus}	
	WHERE ITEM_CODE = #{itemCode}
</update>
</mapper> 
  • Service
package Kh.study.shop.item.service;
public interface ItemService {
	List<ItemVO> selectItemList();
	List<ItemVO> selectRegItemList();
	void updateStock(ItemVO itemVO);
	void updateStautus(ItemVO itemVO);
}
  • ServiceImpl
package Kh.study.shop.item.service;

import java.util.List;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import Kh.study.shop.item.vo.ItemVO;

@Service("itemService")
public class ItemServiceImpl implements ItemService {
	@Autowired
	private SqlSessionTemplate sqlSession;
	
	@Override
	public List<ItemVO> selectItemList() {
		return sqlSession.selectList("itemMapper.selectItemList");
	}
	@Override
	public List<ItemVO> selectRegItemList() {
		return sqlSession.selectList("itemMapper.selectRegItemList");
	}
	@Override
	public void updateStock(ItemVO itemVO) {
		sqlSession.update("itemMapper.updateStock",itemVO);
	}
	@Override
	public void updateStautus(ItemVO itemVO) {
		sqlSession.update("itemMapper.updateStautus",itemVO);
		
	}
}

컬렉션 프레임워크(데이터 모음)

컬렉션 프레임워크(데이터 모음)
: 모두 데이터를 여러개 저장할 수 있는 상자들과 같다

  • List : 순번이 존재하고 중복데이터 저장이 가능하다.
  • Set : 순번이 존재하지 않고 중복데이터 저장이 불가능하다.
    ex) 핸드폰 1개당 중복데이터 저장 불가능 단 하나의 데이터저장만 가능
  • Map : 데이터가 키와 value값이라는 하나의 싸움으로 이루어져있다.

오늘 실습내용

  • 재고수량 변경 및 상품 판매상태 변경 기능 구현
  • 1)alert창을 띄우지 않고 modal창으로 만들어서 버튼클릭시 진행되도록 구현하기
  • 2)상품관리 페이지의 검색조건에서 카테고리 목록 데이터 조회하기
  • 3)등록일 시작날짜는 한달 전 날짜가 세팅, 마지막날짜는 오늘이 기본날짜로 세팅하기
    : 자바스크립트,자바 모두 가능 -> 자바에서 하도록 기능구현하기

상품관리 조건검색 기능

test 폴더 및 파일 추가 생성

: shop프로젝트 내 생성

profile
Dev.Vinch

0개의 댓글