출장등록 완료
This commit is contained in:
parent
589a3cf456
commit
a6093f9961
@ -86,12 +86,13 @@ public class SecurityConfig {
|
||||
.failureHandler(customAuthenticationFailureHandler()) // 로그인 실패 시 핸들러
|
||||
.permitAll() // 로그인 페이지는 누구나 접근 가능
|
||||
)
|
||||
|
||||
// 로그아웃 설정
|
||||
.logout((logoutConfig) -> logoutConfig
|
||||
.logoutUrl("/logout") // 로그아웃 요청 URL
|
||||
.logoutSuccessUrl("/user/login") // 로그아웃 성공 시 리다이렉트 URL
|
||||
.invalidateHttpSession(true) // 로그아웃 시 세션 무효화
|
||||
.deleteCookies("JSESSIONID") // JSESSIONID 쿠키 삭제
|
||||
// .deleteCookies("JSESSIONID") // JSESSIONID 쿠키 삭제
|
||||
.permitAll() // 로그아웃은 누구나 요청 가능
|
||||
)
|
||||
// 세션 관리 설정
|
||||
@ -122,15 +123,31 @@ public class SecurityConfig {
|
||||
// 인증된 사용자 정보를 가져옴
|
||||
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
|
||||
|
||||
// session 설정
|
||||
// 보안이슈로인해 필요한 데이터만
|
||||
UserVO loginVO = new UserVO();
|
||||
loginVO.setUserName( userDetails.getUser().getUserName());
|
||||
request.getSession().setAttribute("loginVO", loginVO);
|
||||
|
||||
// 요청 URL의 호스트를 추출 (디버깅용)
|
||||
String host_url = new URL(request.getRequestURL().toString()).getHost();
|
||||
System.out.println("host_url : " + host_url);
|
||||
// System.out.println("host_url : " + host_url);
|
||||
|
||||
// 로컬 환경이 아닌 경우 로그인 로그를 기록 (2024-05-22 수정)
|
||||
if (!"localhost".equals(host_url)) {
|
||||
|
||||
String id = userDetails.getId();
|
||||
userService.loginLog(id); // 로그인 기록 저장
|
||||
}
|
||||
|
||||
//
|
||||
// var rememberMeServices = new org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices(
|
||||
// "secureAndRandomKey", customUserDetailsService
|
||||
// );
|
||||
// rememberMeServices.setAlwaysRemember(true);
|
||||
// rememberMeServices.onLoginSuccess(request, response, authentication);
|
||||
|
||||
|
||||
response.setStatus(HttpStatus.OK.value()); // HTTP 상태 코드 200 설정
|
||||
response.sendRedirect("/"); // 루트 경로로 리다이렉트
|
||||
};
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
package com.itn.admin.cmn.util.thymeleafUtils;
|
||||
|
||||
import com.itn.admin.itn.code.mapper.domain.CodeDetailVO;
|
||||
import com.itn.admin.itn.code.mapper.domain.CodeVO;
|
||||
import com.itn.admin.itn.code.server.CodeDetailService;
|
||||
import com.itn.admin.itn.user.mapper.UserMapper;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.thymeleaf.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -16,6 +16,8 @@ public class TCodeUtils {
|
||||
|
||||
@Autowired
|
||||
private CodeDetailService codeDetailService;
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
public String getCodeName(String codeGroupId, String codeValue) {
|
||||
if(StringUtils.isEmpty(codeValue)){
|
||||
@ -23,9 +25,42 @@ public class TCodeUtils {
|
||||
};
|
||||
return codeDetailService.getCodeName(codeGroupId, codeValue);
|
||||
}
|
||||
|
||||
public List<CodeDetailVO> getCodeList(String codeGroupId) {
|
||||
return codeDetailService.getDetailsByGroupId(codeGroupId);
|
||||
|
||||
}
|
||||
|
||||
public String getUserName(String uniqId) {
|
||||
if(StringUtils.isEmpty(uniqId)){
|
||||
return uniqId;
|
||||
}
|
||||
String userName = userMapper.findById(uniqId).getUserName();
|
||||
return StringUtils.isEmpty(userName) ? uniqId : userName ;
|
||||
}
|
||||
public String getUserDeptTxt(String uniqId) {
|
||||
String deptCd = userMapper.findById(uniqId).getDeptCd();
|
||||
String dept = "";
|
||||
if(StringUtils.isNotEmpty(deptCd)){
|
||||
dept = codeDetailService.getCodeName("DEPT", deptCd);
|
||||
}
|
||||
return StringUtils.isEmpty(dept) ? "-" : dept ;
|
||||
}
|
||||
|
||||
public String getUserRankTxt(String uniqId) {
|
||||
String rankCd = userMapper.findById(uniqId).getRankCd();
|
||||
String rank = "";
|
||||
if(StringUtils.isNotEmpty(rankCd)){
|
||||
rank = codeDetailService.getCodeName("RANK", rankCd);
|
||||
}
|
||||
return StringUtils.isEmpty(rank) ? "-" : rank ;
|
||||
}
|
||||
|
||||
public String getUserMobilePhone(String uniqId) {
|
||||
String mobilePhone = userMapper.findById(uniqId).getMobilePhone();
|
||||
|
||||
return StringUtils.isEmpty(mobilePhone) ? uniqId : mobilePhone ;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -1,17 +1,45 @@
|
||||
package com.itn.admin.init.web;
|
||||
|
||||
import com.itn.admin.cmn.config.CustomUserDetails;
|
||||
import com.itn.admin.itn.bizTrip.mapper.domain.BizTripVO;
|
||||
import com.itn.admin.itn.bizTrip.service.BizTripService;
|
||||
import com.itn.admin.itn.user.mapper.domain.UserVO;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Controller
|
||||
public class DashboardController {
|
||||
|
||||
@Autowired
|
||||
private BizTripService bizTripService;
|
||||
|
||||
@GetMapping("/")
|
||||
public String index() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
System.out.println("authentication.getAuthorities(); : "+ authentication.getAuthorities());
|
||||
public String index(HttpServletRequest request
|
||||
, Model model
|
||||
, @AuthenticationPrincipal CustomUserDetails loginUser) {
|
||||
|
||||
List<BizTripVO> myApprovalslist = bizTripService.getMyPendingApprovals(loginUser.getUser().getUniqId());
|
||||
|
||||
|
||||
model.addAttribute("myApprovalslist", myApprovalslist);
|
||||
|
||||
// Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
// System.out.println("authentication.getAuthorities(); : "+ authentication.getAuthorities());
|
||||
// HttpSession session = request.getSession();
|
||||
// Object loginVO = session.getAttribute("loginVO");
|
||||
// log.info("세션에 저장된 사용자 정보: {}", loginVO instanceof UserVO);
|
||||
// log.info("세션에 저장된 사용자 정보: {}", loginVO);
|
||||
return "dashboard/index";
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
package com.itn.admin.itn.bizTrip.mapper;
|
||||
|
||||
import com.itn.admin.cmn.msg.RestResponse;
|
||||
import com.itn.admin.itn.bizTrip.mapper.domain.BizTripApprovalVO;
|
||||
import com.itn.admin.itn.bizTrip.mapper.domain.BizTripMemberVO;
|
||||
import com.itn.admin.itn.bizTrip.mapper.domain.BizTripVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -16,4 +18,20 @@ public interface BizTripMapper {
|
||||
void insertApprovalLine(BizTripApprovalVO approval);
|
||||
|
||||
List<BizTripVO> selectTripList();
|
||||
|
||||
BizTripVO getBizTripWithDetail(String tripId);
|
||||
|
||||
List<BizTripVO> getMyPendingApprovals(String uniqId);
|
||||
|
||||
RestResponse saveApproval(BizTripApprovalVO bizTripApprovalVO);
|
||||
|
||||
void updateTripStatus(BizTripVO bizTripVO);
|
||||
|
||||
String getNextApproverId(int tripId, String approverId);
|
||||
|
||||
void updateBizTrip(BizTripVO trip);
|
||||
|
||||
void deleteTripMembers(Integer tripId);
|
||||
|
||||
void deleteApprovalLines(Integer tripId);
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import lombok.experimental.SuperBuilder;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
@Getter
|
||||
@ -22,7 +23,7 @@ public class BizTripApprovalVO extends CmnVO {
|
||||
private String approverId; // 결재자 uniq_id
|
||||
private Integer orderNo; // 결재 순서
|
||||
private String approveStatus; // 결재 상태 (WAIT, APPROVED, REJECTED)
|
||||
private LocalDateTime approveDt; // 결재 일시
|
||||
private Date approveDt; // 결재 일시
|
||||
private String comment; // 결재 의견
|
||||
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import lombok.experimental.SuperBuilder;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Getter
|
||||
@ -26,8 +27,14 @@ public class BizTripVO extends CmnVO {
|
||||
private LocalTime startTime; // 출장 시작시간
|
||||
private LocalTime endTime; // 출장 종료시간
|
||||
private String status; // 결재 상태 (ING, DONE 등)
|
||||
private String useYn; // 사용여부
|
||||
|
||||
|
||||
private String latestApproveStatus; // 최신 결재 상태
|
||||
private String currentApproverId; // 현재 결제 대기자
|
||||
|
||||
private List<BizTripMemberVO> memberList;
|
||||
private List<BizTripApprovalVO> approvalList;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -1,13 +1,26 @@
|
||||
package com.itn.admin.itn.bizTrip.service;
|
||||
|
||||
import com.itn.admin.cmn.msg.RestResponse;
|
||||
import com.itn.admin.itn.bizTrip.mapper.domain.BizTripApprovalVO;
|
||||
import com.itn.admin.itn.bizTrip.mapper.domain.BizTripRequestDTO;
|
||||
import com.itn.admin.itn.bizTrip.mapper.domain.BizTripVO;
|
||||
import com.itn.admin.itn.user.mapper.domain.UserVO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface BizTripService {
|
||||
RestResponse register(BizTripRequestDTO dto);
|
||||
|
||||
List<BizTripVO> selectTripList();
|
||||
|
||||
Map<String, Object> getBizTripWithDetail(String tripId);
|
||||
|
||||
List<BizTripVO> getMyPendingApprovals(String uniqId);
|
||||
|
||||
RestResponse approval(BizTripApprovalVO bizTripApprovalVO, UserVO userVO);
|
||||
|
||||
Map<String, Object> getBizTripWithEdit(String tripId);
|
||||
|
||||
RestResponse update(BizTripRequestDTO dto);
|
||||
}
|
||||
|
||||
@ -10,11 +10,14 @@ import com.itn.admin.itn.bizTrip.service.BizTripService;
|
||||
import com.itn.admin.itn.code.mapper.CodeMapper;
|
||||
import com.itn.admin.itn.code.mapper.domain.CodeVO;
|
||||
import com.itn.admin.itn.code.server.CodeService;
|
||||
import com.itn.admin.itn.user.mapper.domain.UserVO;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
public class BizTripServiceImpl implements BizTripService {
|
||||
@ -49,12 +52,146 @@ public class BizTripServiceImpl implements BizTripService {
|
||||
}
|
||||
|
||||
|
||||
return new RestResponse(HttpStatus.OK, "등록되었습니다");
|
||||
return new RestResponse(HttpStatus.OK, "등록되었습니다",tripId);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RestResponse update(BizTripRequestDTO dto) {
|
||||
BizTripVO trip = dto.getTripInfo();
|
||||
Integer tripId = trip.getTripId();
|
||||
|
||||
// 1. 출장 기본 정보 수정
|
||||
bizTripMapper.updateBizTrip(trip); // tripId 포함되어 있어야 함
|
||||
|
||||
// 2. 기존 출장 인원 삭제 후 재등록
|
||||
bizTripMapper.deleteTripMembers(tripId);
|
||||
List<BizTripMemberVO> memberList = dto.getTripMembers();
|
||||
if (memberList != null && !memberList.isEmpty()) {
|
||||
for (BizTripMemberVO member : memberList) {
|
||||
member.setTripId(tripId);
|
||||
bizTripMapper.insertTripMember(member);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 기존 결재라인 삭제 후 재등록
|
||||
bizTripMapper.deleteApprovalLines(tripId);
|
||||
List<BizTripApprovalVO> approvalList = dto.getApprovalLines();
|
||||
if (approvalList != null && !approvalList.isEmpty()) {
|
||||
for (BizTripApprovalVO approval : approvalList) {
|
||||
approval.setTripId(tripId);
|
||||
bizTripMapper.insertApprovalLine(approval);
|
||||
}
|
||||
}
|
||||
|
||||
return new RestResponse(HttpStatus.OK, "수정되었습니다");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public List<BizTripVO> selectTripList() {
|
||||
return bizTripMapper.selectTripList();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getBizTripWithDetail(String tripId) {
|
||||
Map<String, Object> returnMap = new HashMap<>();
|
||||
BizTripVO bizTrip = bizTripMapper.getBizTripWithDetail(tripId);
|
||||
|
||||
// 상세화면
|
||||
String firstWaitingApproverId = bizTrip.getApprovalList().stream()
|
||||
.filter(a -> "10".equals(a.getApproveStatus()))
|
||||
.map(BizTripApprovalVO::getApproverId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
|
||||
/*더 필요한 데이터있으면 추가해서 returnMap에 put 해서 넘겨주기*/
|
||||
returnMap.put("bizTrip", bizTrip);
|
||||
|
||||
returnMap.put("firstWaitingApproverId", firstWaitingApproverId);
|
||||
return returnMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getBizTripWithEdit(String tripId) {
|
||||
Map<String, Object> returnMap = new HashMap<>();
|
||||
BizTripVO bizTrip = bizTripMapper.getBizTripWithDetail(tripId);
|
||||
|
||||
// 검토1, 검토2, 결제를 구현하기 위한 로직
|
||||
List<BizTripApprovalVO> finalList = new ArrayList<>();
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
// 복사해서 final 변수로 만듦
|
||||
// 불변이라고 선언을 해야 람다에서 사용가능
|
||||
final int order = i;
|
||||
Optional<BizTripApprovalVO> found = bizTrip.getApprovalList().stream()
|
||||
.filter(vo -> vo.getOrderNo() == order)
|
||||
.findFirst();
|
||||
|
||||
if (found.isPresent()) {
|
||||
finalList.add(found.get());
|
||||
} else {
|
||||
BizTripApprovalVO empty = new BizTripApprovalVO();
|
||||
empty.setOrderNo(i);
|
||||
finalList.add(empty);
|
||||
}
|
||||
}
|
||||
bizTrip.setApprovalList(finalList);
|
||||
// -- 검토1, 검토2, 결제를 구현하기 위한 로직
|
||||
|
||||
/*더 필요한 데이터있으면 추가해서 returnMap에 put 해서 넘겨주기*/
|
||||
returnMap.put("bizTrip", bizTrip);
|
||||
|
||||
return returnMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BizTripVO> getMyPendingApprovals(String uniqId) {
|
||||
return bizTripMapper.getMyPendingApprovals(uniqId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public RestResponse approval(BizTripApprovalVO bizTripApprovalVO, UserVO userVO) {
|
||||
|
||||
|
||||
// '결재 상태(APPR_STS - 대기:10, 승인:30, 거절:40)'
|
||||
String approveStatus = bizTripApprovalVO.getApproveStatus();
|
||||
int tripId = bizTripApprovalVO.getTripId();
|
||||
|
||||
bizTripMapper.saveApproval(bizTripApprovalVO);
|
||||
|
||||
|
||||
// 다음 결제자 있는지 확인하는 로직
|
||||
// 있으면 uniqId 가져옴
|
||||
String nextApproverId = bizTripMapper.getNextApproverId(tripId, bizTripApprovalVO.getApproverId());
|
||||
BizTripVO bizTripVO = BizTripVO.builder()
|
||||
.tripId(tripId)
|
||||
.status(approveStatus)
|
||||
.lastUpdusrId(userVO.getUniqId())
|
||||
.build();
|
||||
|
||||
if (StringUtils.isNotEmpty(nextApproverId) && "30".equals(approveStatus)) {
|
||||
bizTripVO.setStatus("20");
|
||||
// 다음 결재자 있음 & 현재 승인 → '진행중'
|
||||
bizTripMapper.updateTripStatus(bizTripVO);
|
||||
|
||||
//TODO [slack] nextApproverId를 참고해서 알려줘야함
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
// 마지막 결재자 or 반려 → 본인 상태 그대로 반영
|
||||
bizTripMapper.updateTripStatus(bizTripVO);
|
||||
|
||||
//TODO [slack] 결제 완료되었다는걸 알려줘야함
|
||||
|
||||
}
|
||||
|
||||
|
||||
return new RestResponse(HttpStatus.OK,"결제가 완료되었습니다.");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.itn.admin.itn.bizTrip.web;
|
||||
|
||||
import com.itn.admin.cmn.config.CustomUserDetails;
|
||||
import com.itn.admin.itn.bizTrip.mapper.domain.BizTripMemberVO;
|
||||
import com.itn.admin.itn.bizTrip.mapper.domain.BizTripVO;
|
||||
import com.itn.admin.itn.bizTrip.service.BizTripService;
|
||||
import com.itn.admin.itn.user.mapper.domain.UserVO;
|
||||
@ -12,8 +13,11 @@ import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Controller
|
||||
@ -32,11 +36,43 @@ public class BizTripController {
|
||||
|
||||
return "itn/bizTrip/reg";
|
||||
}
|
||||
|
||||
@GetMapping("/itn/bizTrip/edit/{tripId}")
|
||||
public String edit(@PathVariable String tripId
|
||||
, @AuthenticationPrincipal CustomUserDetails loginUser
|
||||
,Model model
|
||||
) {
|
||||
|
||||
Map<String, Object> returnMap = bizTripService.getBizTripWithEdit(tripId);
|
||||
model.addAttribute("trip", returnMap.get("bizTrip"));
|
||||
model.addAttribute("loginUser", loginUser.getUser());
|
||||
|
||||
return "itn/bizTrip/edit";
|
||||
}
|
||||
@GetMapping("/itn/bizTrip/list")
|
||||
public String bizTripList(Model model) {
|
||||
public String bizTripList(Model model
|
||||
,@AuthenticationPrincipal CustomUserDetails loginUser) {
|
||||
List<BizTripVO> list = bizTripService.selectTripList();
|
||||
model.addAttribute("list", list);
|
||||
for (BizTripVO vo : list) {
|
||||
log.info(" + vo :: [{}]", vo.getCurrentApproverId());
|
||||
}
|
||||
model.addAttribute("loginUser", loginUser.getUser());
|
||||
log.info(" + loginUser :: [{}]", loginUser.getUser());
|
||||
return "itn/bizTrip/list"; // Thymeleaf HTML 파일 경로
|
||||
}
|
||||
|
||||
@GetMapping("/itn/bizTrip/detail/{tripId}")
|
||||
public String tripDetail(@PathVariable String tripId
|
||||
,@AuthenticationPrincipal CustomUserDetails loginUser
|
||||
, Model model
|
||||
) {
|
||||
Map<String, Object> returnMap = bizTripService.getBizTripWithDetail(tripId);
|
||||
model.addAttribute("trip", returnMap.get("bizTrip"));
|
||||
model.addAttribute("loginUser", loginUser.getUser());
|
||||
model.addAttribute("firstWaitingApproverId", returnMap.get("firstWaitingApproverId"));
|
||||
return "itn/bizTrip/tripDetail";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package com.itn.admin.itn.bizTrip.web;
|
||||
|
||||
import com.itn.admin.cmn.config.CustomUserDetails;
|
||||
import com.itn.admin.cmn.msg.RestResponse;
|
||||
import com.itn.admin.itn.bizTrip.mapper.domain.BizTripApprovalVO;
|
||||
import com.itn.admin.itn.bizTrip.mapper.domain.BizTripRequestDTO;
|
||||
import com.itn.admin.itn.bizTrip.service.BizTripService;
|
||||
import com.itn.admin.itn.code.mapper.domain.CodeDetailVO;
|
||||
@ -27,11 +28,24 @@ public class BizTripRestController {
|
||||
, @AuthenticationPrincipal CustomUserDetails loginUser) {
|
||||
dto.getTripInfo().setFrstRegisterId(loginUser.getUser().getUniqId());
|
||||
log.info("dto: [{}]", dto);
|
||||
// bizTripService.register(dto);
|
||||
return ResponseEntity.ok().body(bizTripService.register(dto));
|
||||
}
|
||||
|
||||
@PostMapping("/api/bizTrip/approval")
|
||||
public ResponseEntity<RestResponse> approval(@RequestBody BizTripApprovalVO bizTripApprovalVO
|
||||
, @AuthenticationPrincipal CustomUserDetails loginUser) {
|
||||
log.info("bizTripApprovalVO: [{}]", bizTripApprovalVO);
|
||||
// bizTripService.register(dto);
|
||||
return ResponseEntity.ok().body(bizTripService.approval(bizTripApprovalVO, loginUser.getUser()));
|
||||
}
|
||||
|
||||
@PutMapping("/api/bizTrip/update")
|
||||
public ResponseEntity<RestResponse> updateBizTrip(@RequestBody BizTripRequestDTO dto,
|
||||
@AuthenticationPrincipal CustomUserDetails loginUser) {
|
||||
dto.getTripInfo().setLastUpdusrId(loginUser.getUser().getUniqId());
|
||||
return ResponseEntity.ok().body(bizTripService.update(dto));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -38,12 +38,13 @@ public class UserRestController {
|
||||
}
|
||||
|
||||
|
||||
// 특정 코드 그룹을 ID로 가져오는 메서드
|
||||
//
|
||||
@GetMapping("/api/admin/user/{uniqId}")
|
||||
public ResponseEntity<?> findByUniqId(@PathVariable String uniqId) {
|
||||
return ResponseEntity.ok(userService.findByUniqId(uniqId));
|
||||
}
|
||||
// 특정 코드 그룹을 ID로 가져오는 메서드
|
||||
|
||||
//
|
||||
@GetMapping("/api/admin/user/search/name")
|
||||
public ResponseEntity<?> findByUniqUserName(@RequestParam String userName) {
|
||||
log.info("userName: {}", userName);
|
||||
|
||||
@ -67,40 +67,118 @@
|
||||
ELSE '10' /* 전부 대기 */
|
||||
END AS status
|
||||
|
||||
/* 현재 결재 대기자 추가 */
|
||||
,(
|
||||
SELECT a.approver_id
|
||||
FROM biz_trip_approval a
|
||||
WHERE a.trip_id = bt.trip_id
|
||||
AND a.approve_status = '10'
|
||||
ORDER BY a.order_no ASC
|
||||
LIMIT 1
|
||||
) AS current_approver_id
|
||||
|
||||
FROM biz_trip bt
|
||||
WHERE bt.use_yn = 'Y'
|
||||
ORDER BY bt.trip_dt DESC
|
||||
]]>
|
||||
</select>
|
||||
|
||||
<!--
|
||||
<!– 모든 코드 그룹을 조회하는 쿼리 –>
|
||||
<select id="findAll" resultType="codeVO">
|
||||
SELECT * FROM common_code
|
||||
<!-- 출장 상세조회 + 하위 정보까지 포함하는 resultMap 정의 -->
|
||||
<resultMap id="tripDetailMap" type="bizTripVO">
|
||||
<!-- 출장 ID를 기준으로 기본 정보 바인딩 -->
|
||||
<id property="tripId" column="trip_id"/>
|
||||
<!-- 나머지 속성들은 mapUnderscoreToCamelCase 설정으로 자동 매핑됨 -->
|
||||
|
||||
<!-- 출장 인원 목록: trip_id를 기준으로 getTripMembers 쿼리 실행 -->
|
||||
<collection property="memberList" ofType="bizTripMemberVO" column="trip_id" select="getTripMembers"/>
|
||||
<!-- 결재 라인 목록: trip_id를 기준으로 getTripApprovals 쿼리 실행 -->
|
||||
<collection property="approvalList" ofType="bizTripApprovalVO" column="trip_id" select="getTripApprovals"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 출장 기본 정보 단건 조회 -->
|
||||
<select id="getBizTripWithDetail" resultMap="tripDetailMap">
|
||||
SELECT * FROM biz_trip WHERE trip_id = #{tripId}
|
||||
</select>
|
||||
|
||||
<!– 특정 코드 그룹을 ID로 조회하는 쿼리 –>
|
||||
<select id="findById" parameterType="String" resultType="codeVO">
|
||||
SELECT * FROM common_code WHERE code_group_id = #{codeGroupId}
|
||||
<!-- 출장 인원 목록 조회 -->
|
||||
<select id="getTripMembers" resultType="bizTripMemberVO">
|
||||
SELECT * FROM biz_trip_member WHERE trip_id = #{tripId}
|
||||
</select>
|
||||
|
||||
<!– 코드 그룹을 추가하는 쿼리 –>
|
||||
<insert id="insert" parameterType="codeVO">
|
||||
INSERT INTO common_code (code_group_id, code_group_name, description, frst_register_id, frst_regist_pnttm, last_updusr_id, last_updt_pnttm)
|
||||
VALUES (#{codeGroupId}, #{codeGroupName}, #{description}, #{frstRegisterId}, #{frstRegistPnttm}, #{lastUpdusrId}, #{lastUpdtPnttm})
|
||||
</insert>
|
||||
<!-- 출장 결재 라인 목록 조회 -->
|
||||
<select id="getTripApprovals" resultType="bizTripApprovalVO">
|
||||
SELECT * FROM biz_trip_approval WHERE trip_id = #{tripId}
|
||||
</select>
|
||||
|
||||
<!– 코드 그룹을 수정하는 쿼리 –>
|
||||
<update id="update" parameterType="codeVO">
|
||||
UPDATE common_code
|
||||
SET code_group_name = #{codeGroupName},
|
||||
description = #{description},
|
||||
<select id="getMyPendingApprovals" resultType="bizTripVO">
|
||||
SELECT bt.*
|
||||
FROM biz_trip bt
|
||||
JOIN biz_trip_approval bta ON bt.trip_id = bta.trip_id
|
||||
WHERE bta.approver_id = #{uniqId}
|
||||
AND bta.approve_status = '10'
|
||||
AND bta.order_no = (
|
||||
SELECT MIN(bta2.order_no)
|
||||
FROM biz_trip_approval bta2
|
||||
WHERE bta2.trip_id = bt.trip_id
|
||||
AND bta2.approve_status = '10'
|
||||
)
|
||||
ORDER BY bt.trip_dt DESC
|
||||
</select>
|
||||
<select id="saveApproval" resultType="bizTripApprovalVO">
|
||||
UPDATE biz_trip_approval
|
||||
SET approve_status = #{approveStatus},
|
||||
approve_dt = NOW()
|
||||
WHERE id = #{id}
|
||||
AND approver_id = #{approverId}
|
||||
</select>
|
||||
|
||||
<update id="updateTripStatus">
|
||||
UPDATE biz_trip
|
||||
SET status = #{status},
|
||||
last_updusr_id = #{lastUpdusrId},
|
||||
last_updt_pnttm = #{lastUpdtPnttm}
|
||||
WHERE code_group_id = #{codeGroupId}
|
||||
last_updt_pnttm = NOW()
|
||||
WHERE trip_id = #{tripId}
|
||||
</update>
|
||||
|
||||
<!– 코드 그룹을 삭제하는 쿼리 –>
|
||||
<delete id="delete" parameterType="String">
|
||||
DELETE FROM common_code WHERE code_group_id = #{codeGroupId}
|
||||
</delete>-->
|
||||
<select id="getNextApproverId" resultType="string">
|
||||
SELECT approver_id
|
||||
FROM biz_trip_approval
|
||||
WHERE trip_id = #{tripId}
|
||||
AND order_no > (
|
||||
SELECT order_no
|
||||
FROM biz_trip_approval
|
||||
WHERE trip_id = #{tripId}
|
||||
AND approver_id = #{approverId}
|
||||
)
|
||||
ORDER BY order_no
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<update id="updateBizTrip" parameterType="bizTripVO">
|
||||
UPDATE biz_trip
|
||||
SET
|
||||
trip_type_cd = #{tripTypeCd},
|
||||
location_cd = #{locationCd},
|
||||
location_txt = #{locationTxt},
|
||||
purpose = #{purpose},
|
||||
move_cd = #{moveCd},
|
||||
trip_dt = #{tripDt},
|
||||
start_time = #{startTime},
|
||||
end_time = #{endTime},
|
||||
status = #{status},
|
||||
last_updusr_id = #{lastUpdusrId},
|
||||
last_updt_pnttm = NOW()
|
||||
WHERE trip_id = #{tripId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteTripMembers" parameterType="int">
|
||||
DELETE FROM biz_trip_member
|
||||
WHERE trip_id = #{tripId}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteApprovalLines" parameterType="int">
|
||||
DELETE FROM biz_trip_approval
|
||||
WHERE trip_id = #{tripId}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
@ -54,7 +54,7 @@
|
||||
,ic.holi_code
|
||||
-- ,ic.pstn
|
||||
,us.user_name
|
||||
,us.user_rank
|
||||
,us.rank_cd
|
||||
from itn_commute ic
|
||||
left join itn_commute_group icg
|
||||
on ic.commute_group_id = icg.commute_group_id
|
||||
|
||||
74
src/main/resources/static/cmn/js/bizTrip/edit/service.js
Normal file
74
src/main/resources/static/cmn/js/bizTrip/edit/service.js
Normal file
@ -0,0 +1,74 @@
|
||||
/**
|
||||
* @discription 수정 액션
|
||||
* @param tripId
|
||||
*/
|
||||
function updateTripData(tripId) {
|
||||
if (!validateTripForm()) return;
|
||||
|
||||
// 값 생성
|
||||
const tripInfo = {
|
||||
tripId: tripId,
|
||||
tripTypeCd: $('#tripType').val(),
|
||||
locationCd: $('#tripLocation').val(),
|
||||
locationTxt: $('#locationTxt').val(),
|
||||
purpose: $('#purpose').val(),
|
||||
moveCd: $('#tripMove').val(),
|
||||
tripDt: $('#tripDate').val(),
|
||||
startTime: $('#startTimePicker input').val(),
|
||||
endTime: $('#endTimePicker input').val(),
|
||||
status: '10'
|
||||
};
|
||||
|
||||
const tripMembers = [];
|
||||
$('#tripMemberTbody tr').each(function () {
|
||||
const uniqId = $(this).data('uniqid');
|
||||
if (!uniqId) return;
|
||||
const role = $(this).find('td').eq(3).text().trim() === '기안자' ? '0' : '1';
|
||||
tripMembers.push({ uniqId, role });
|
||||
});
|
||||
|
||||
const approvalLines = [];
|
||||
['approver1', 'approver2', 'approver3'].forEach((id, idx) => {
|
||||
const uniqId = $(`#${id}`).val();
|
||||
if (uniqId) {
|
||||
approvalLines.push({
|
||||
approverId: uniqId,
|
||||
orderNo: idx + 1,
|
||||
approveStatus: '10'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const payload = { tripInfo, tripMembers, approvalLines };
|
||||
console.log("update payload:", payload);
|
||||
|
||||
// Ajax 전송
|
||||
$.ajax({
|
||||
url: '/api/bizTrip/update',
|
||||
method: 'PUT',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(payload),
|
||||
success: function(data) {
|
||||
console.log('data : ', data);
|
||||
// Toast 먼저 띄움 (바로 표시됨)
|
||||
// fn_successAlert("수정 성공", data.msg);
|
||||
|
||||
Swal.fire({
|
||||
title: data.msg,
|
||||
text: '목록으로 이동하시겠습니까?',
|
||||
icon: 'success',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: '이동',
|
||||
width: 300,
|
||||
cancelButtonText: '취소'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
location.href = "/itn/bizTrip/list";
|
||||
} else if (result.isDismissed) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
},
|
||||
error: () => fn_failedAlert("수정 실패", "오류가 발생했습니다.")
|
||||
});
|
||||
}
|
||||
247
src/main/resources/static/cmn/js/bizTrip/reg/event.js
Normal file
247
src/main/resources/static/cmn/js/bizTrip/reg/event.js
Normal file
@ -0,0 +1,247 @@
|
||||
/**
|
||||
* 페이지 로드 완료 후 실행되는 초기화 함수 (비워둠)
|
||||
* @function
|
||||
*/
|
||||
$(function () {
|
||||
// 초기화 코드 필요 시 여기에 작성
|
||||
});
|
||||
|
||||
/**
|
||||
* Enter 키 입력 시 사용자 검색 수행
|
||||
* @event keydown
|
||||
* @param {KeyboardEvent} e
|
||||
*/
|
||||
document.getElementById("userSearchKeyword").addEventListener("keydown", function (e) {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
searchUser();
|
||||
}
|
||||
});
|
||||
document.getElementById("approvalSearchKeyword").addEventListener("keydown", function (e) {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
searchApproval();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 모달이 열릴 때 사용자 목록을 불러오고 포커스를 맞춤
|
||||
* @event shown.bs.modal
|
||||
*/
|
||||
$('#userSearchModal').on('shown.bs.modal', function () {
|
||||
loadUserList();
|
||||
$('#userSearchKeyword').trigger('focus');
|
||||
});
|
||||
$('#approvalSearchModal').on('shown.bs.modal', function () {
|
||||
loadApprovalList();
|
||||
$('#approvalSearchKeyword').trigger('focus');
|
||||
});
|
||||
|
||||
/**
|
||||
* 날짜 입력 필드 클릭 시 브라우저 기본 날짜 선택기 표시
|
||||
* @event click
|
||||
*/
|
||||
document.querySelector('input[id="tripDate"]').addEventListener('click', function () {
|
||||
this.showPicker && this.showPicker();
|
||||
});
|
||||
|
||||
/**
|
||||
* 사용자 목록을 불러오는 Ajax 요청 함수
|
||||
* @function
|
||||
* @param {string} [keyword=""] - 검색 키워드
|
||||
* @returns {void}
|
||||
*/
|
||||
function loadUserList(keyword = "") {
|
||||
$.ajax({
|
||||
url: '/api/admin/user/search/name',
|
||||
type: 'GET',
|
||||
data: { userName: keyword },
|
||||
success: function (result) {
|
||||
const tbody = document.getElementById("userSearchResult");
|
||||
tbody.innerHTML = "";
|
||||
|
||||
const data = result.data;
|
||||
if (data && data.length > 0) {
|
||||
data.forEach(user => {
|
||||
const row = document.createElement("tr");
|
||||
row.innerHTML = `
|
||||
<td>${user.userName}</td>
|
||||
<td>${user.deptNm || "-"}</td>
|
||||
<td>${user.rankCd || "-"}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-success"
|
||||
onclick="selectUser('${user.userName}', '${user.deptNm}', '${user.mobilePhone}', '${user.uniqId}')">
|
||||
<i class="fas fa-user-plus"></i> 추가
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
} else {
|
||||
tbody.innerHTML = `<tr><td colspan="5" class="text-center text-muted">검색 결과가 없습니다.</td></tr>`;
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
alert("사용자 목록을 불러오는 데 실패했습니다.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadApprovalList(keyword = "") {
|
||||
$.ajax({
|
||||
url: '/api/admin/approval/search/name',
|
||||
type: 'GET',
|
||||
data: { userName: keyword },
|
||||
success: function (result) {
|
||||
const tbody = document.getElementById("approvalSearchResult");
|
||||
const data = result.data;
|
||||
|
||||
tbody.innerHTML = "";
|
||||
if (data && data.length > 0) {
|
||||
data.forEach(user => {
|
||||
const row = document.createElement("tr");
|
||||
row.innerHTML = `
|
||||
<td>${user.userName}</td>
|
||||
<td>${user.deptNm || "-"}</td>
|
||||
<td>${user.rankCd || "-"}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-success"
|
||||
onclick="selectApproval('${user.userName}', '${user.deptNm}', '${user.mobilePhone}', '${user.uniqId}')">
|
||||
<i class="fas fa-user-plus"></i> 추가
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
} else {
|
||||
tbody.innerHTML = `<tr><td colspan="5" class="text-center text-muted">검색 결과가 없습니다.</td></tr>`;
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
alert("사용자 목록을 불러오는 데 실패했습니다.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자를 선택하여 출장 인원 또는 결재라인에 추가
|
||||
* @function
|
||||
* @param {string} name - 사용자 이름
|
||||
* @param {string} dept - 부서명
|
||||
* @param {string} phone - 전화번호
|
||||
* @param {string} uniqId - 사용자 고유 ID
|
||||
* @returns {void}
|
||||
*/
|
||||
function selectUser(name, dept, phone, uniqId) {
|
||||
|
||||
const tbody = document.getElementById("tripMemberTbody");
|
||||
const newRow = document.createElement("tr");
|
||||
newRow.setAttribute("data-uniqid", uniqId);
|
||||
newRow.innerHTML = `
|
||||
<td>${name}</td>
|
||||
<td>${dept}</td>
|
||||
<td>${phone}</td>
|
||||
<td class="text-center">
|
||||
<a href="#" class="btn btn-danger btn-sm" onclick="removeMemberRow(this)">
|
||||
<i class="fas fa-user-minus"></i> 삭제
|
||||
</a>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(newRow);
|
||||
$('#userSearchModal').modal('hide');
|
||||
}
|
||||
|
||||
|
||||
function selectApproval(name, dept, phone, uniqId) {
|
||||
const stageId = $('#approvalSearchModal').data('stageId');
|
||||
|
||||
const rowMap = {
|
||||
approval1: { index: 0, inputId: "approver1" },
|
||||
approval2: { index: 1, inputId: "approver2" },
|
||||
approval3: { index: 2, inputId: "approver3" }
|
||||
};
|
||||
|
||||
const { index, inputId } = rowMap[stageId];
|
||||
const tbody = document.getElementById("approvalLineTbody");
|
||||
const targetRow = tbody.rows[index];
|
||||
if (!targetRow) return;
|
||||
|
||||
targetRow.innerHTML = `
|
||||
<td class="text-center align-middle">${targetRow.cells[0].textContent}</td>
|
||||
<td>${name}</td>
|
||||
<td>${dept}</td>
|
||||
<td>${phone}</td>
|
||||
<td class="text-center">
|
||||
<a href="#" class="btn btn-danger btn-sm" onclick="resetApproval('${stageId}')">
|
||||
<i class="fas fa-user-minus"></i> 삭제
|
||||
</a>
|
||||
</td>
|
||||
`;
|
||||
|
||||
// hidden input에 uniqId 저장
|
||||
document.getElementById(inputId).value = uniqId;
|
||||
|
||||
$('#approvalSearchModal').modal('hide');
|
||||
$('#approvalSearchModal').data('stageId', null);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 검색창 버튼 클릭 시 사용자 검색 실행
|
||||
* @function
|
||||
*/
|
||||
function searchUser() {
|
||||
const keyword = document.getElementById("userSearchKeyword").value.trim();
|
||||
loadUserList(keyword);
|
||||
}
|
||||
function searchApproval() {
|
||||
const keyword = document.getElementById("userSearchKeyword").value.trim();
|
||||
loadApprovalList(keyword);
|
||||
}
|
||||
|
||||
/**
|
||||
* 출장 인원 행 삭제
|
||||
* @function
|
||||
* @param {HTMLElement} el - 삭제 버튼 요소
|
||||
*/
|
||||
function removeMemberRow(el) {
|
||||
if (confirm("정말 삭제하시겠습니까?")) {
|
||||
const row = el.closest("tr");
|
||||
if (row) row.remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 결재자 지정 모달 열기
|
||||
* @function
|
||||
* @param {string} stageId - 결재 단계 ID (approval1, approval2, approval3)
|
||||
*/
|
||||
function openApprovalModal(stageId) {
|
||||
$('#approvalSearchModal').data('stageId', stageId).modal('show');
|
||||
}
|
||||
|
||||
/**
|
||||
* 결재자 삭제 및 단계 초기화
|
||||
* @function
|
||||
* @param {string} stageId - 결재 단계 ID
|
||||
*/
|
||||
function resetApproval(stageId) {
|
||||
const rowIndex = {
|
||||
approval1: 0,
|
||||
approval2: 1,
|
||||
approval3: 2
|
||||
}[stageId];
|
||||
|
||||
const tbody = document.getElementById("approvalLineTbody");
|
||||
const label = ['검토 1', '검토 2', '결제'][rowIndex];
|
||||
|
||||
tbody.rows[rowIndex].innerHTML = `
|
||||
<td class="text-center align-middle">${label}</td>
|
||||
<td colspan="4">
|
||||
<button type="button" class="btn btn-outline-info btn-sm" onclick="openApprovalModal('${stageId}')">
|
||||
<i class="fas fa-user-plus"></i> 사용자 지정
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
}
|
||||
26
src/main/resources/static/cmn/js/bizTrip/reg/init.js
Normal file
26
src/main/resources/static/cmn/js/bizTrip/reg/init.js
Normal file
@ -0,0 +1,26 @@
|
||||
|
||||
$(function () {
|
||||
|
||||
/**
|
||||
* 시작 시간 선택기 초기화
|
||||
* @function
|
||||
*/
|
||||
$('#startTimePicker').datetimepicker({
|
||||
format: 'HH:mm',
|
||||
stepping: 10
|
||||
});
|
||||
|
||||
/**
|
||||
* 종료 시간 선택기 초기화
|
||||
* @function
|
||||
*/
|
||||
$('#endTimePicker').datetimepicker({
|
||||
format: 'HH:mm',
|
||||
stepping: 10,
|
||||
icons: {
|
||||
time: 'far fa-clock',
|
||||
up: 'fas fa-chevron-up',
|
||||
down: 'fas fa-chevron-down'
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -46,7 +46,25 @@ function collectAndSubmitTripData() {
|
||||
method: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(payload),
|
||||
success: () => fn_successAlert("등록 성공", "출장 정보가 저장되었습니다."),
|
||||
success: function(data) {
|
||||
|
||||
Swal.fire({
|
||||
title: "출장 정보가 저장되었습니다.",
|
||||
text: '상세페이지로 이동하시겠습니까?',
|
||||
icon: 'success',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: '상세',
|
||||
cancelButtonText: '목록'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
location.href = "/itn/bizTrip/detail/"+data.data;
|
||||
} else if (result.isDismissed) {
|
||||
location.href = "/itn/bizTrip/list";
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
error: () => fn_failedAlert("등록 실패", "오류가 발생했습니다.")
|
||||
});
|
||||
}
|
||||
65
src/main/resources/static/cmn/js/bizTrip/reg/validation.js
Normal file
65
src/main/resources/static/cmn/js/bizTrip/reg/validation.js
Normal file
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 출장 등록 입력값 유효성 검사 함수
|
||||
* - 필수 항목 누락 여부 확인
|
||||
* - 출장일자가 오늘 이전인지 확인
|
||||
* - 시작 시간이 종료 시간보다 늦은지 확인
|
||||
*
|
||||
* @returns {boolean} 유효하면 true, 유효하지 않으면 false
|
||||
*/
|
||||
function validateTripForm() {
|
||||
// ===== [1] 입력값 가져오기 =====
|
||||
const tripType = $('#tripType');
|
||||
const location = $('#tripLocation');
|
||||
const locationTxt = $('#locationTxt');
|
||||
const purpose = $('#purpose');
|
||||
const move = $('#tripMove');
|
||||
const date = $('#tripDate');
|
||||
const start = $('#startTimePicker input');
|
||||
const end = $('#endTimePicker input');
|
||||
const approver = $('#approver3');
|
||||
|
||||
const tripTypeCd = tripType.val();
|
||||
const locationCd = location.val();
|
||||
const locationTxtVal = locationTxt.val();
|
||||
const purposeVal = purpose.val();
|
||||
const moveCd = move.val();
|
||||
const tripDt = date.val();
|
||||
const startTime = start.val();
|
||||
const endTime = end.val();
|
||||
const approver3 = approver.val();
|
||||
|
||||
// ===== [2] 필수값 체크 + focus =====
|
||||
if (!tripTypeCd) return fn_failedAlert("입력 오류", "출장 구분을 선택해주세요.", 1000), tripType.focus(), false;
|
||||
if (!locationCd) return fn_failedAlert("입력 오류", "출장지를 선택해주세요.", 1000), location.focus(), false;
|
||||
if (!locationTxtVal || locationTxtVal.trim() === "") return fn_failedAlert("입력 오류", "목적지를 입력해주세요.", 1000), locationTxt.focus(), false;
|
||||
if (!purposeVal || purposeVal.trim() === "") return fn_failedAlert("입력 오류", "출장 목적을 입력해주세요.", 1000), purpose.focus(), false;
|
||||
if (!moveCd) return fn_failedAlert("입력 오류", "이동 수단을 선택해주세요.", 1000), move.focus(), false;
|
||||
if (!tripDt) return fn_failedAlert("입력 오류", "출장일자를 선택해주세요.", 1000), date.focus(), false;
|
||||
if (!startTime || !endTime) return fn_failedAlert("입력 오류", "시작/종료 시간을 입력해주세요.", 1000), start.focus(), false;
|
||||
if (!approver3) return fn_failedAlert("입력 오류", "최종 결재자를 지정해주세요.", 1000), approver.focus(), false;
|
||||
|
||||
// ===== [3] 출장일자가 오늘보다 과거일 경우 =====
|
||||
const today = new Date();
|
||||
const inputDate = new Date(tripDt);
|
||||
today.setHours(0, 0, 0, 0);
|
||||
inputDate.setHours(0, 0, 0, 0);
|
||||
if (inputDate < today) {
|
||||
fn_failedAlert("입력 오류", "출장일자는 오늘보다 빠를 수 없습니다.");
|
||||
date.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
// ===== [4] 시간 순서 확인 (종료 > 시작) =====
|
||||
const [sH, sM] = startTime.split(':').map(Number);
|
||||
const [eH, eM] = endTime.split(':').map(Number);
|
||||
const startDate = new Date(); startDate.setHours(sH, sM, 0, 0);
|
||||
const endDate = new Date(); endDate.setHours(eH, eM, 0, 0);
|
||||
if (endDate <= startDate) {
|
||||
fn_failedAlert("입력 오류", "종료 시간은 시작 시간보다 이후여야 합니다.");
|
||||
end.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
// ===== [5] 유효성 통과 =====
|
||||
return true;
|
||||
}
|
||||
@ -6,8 +6,10 @@
|
||||
layout:decorate="layout">
|
||||
<head>
|
||||
<!-- layout.html 에 들어간 head 부분을 제외하고 개별 파일에만 적용되는 head 부분 추가 -->
|
||||
<title>401</title>
|
||||
|
||||
<th:block layout:fragment="title">
|
||||
<title>401</title>
|
||||
</th:block>
|
||||
<!-- 필요하다면 개별 파일에 사용될 css/js 선언 -->
|
||||
|
||||
<link rel="stylesheet" th:href="@{/plugins/datatables-bs4/css/dataTables.bootstrap4.min.css}">
|
||||
|
||||
@ -111,512 +111,52 @@
|
||||
<!-- Main row -->
|
||||
<div class="row">
|
||||
<!-- Left col -->
|
||||
<section class="col-lg-7 connectedSortable">
|
||||
<!-- Custom tabs (Charts with tabs)-->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">
|
||||
<i class="fas fa-chart-pie mr-1"></i>
|
||||
Sales
|
||||
</h3>
|
||||
<div class="card-tools">
|
||||
<ul class="nav nav-pills ml-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#revenue-chart" data-toggle="tab">Area</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#sales-chart" data-toggle="tab">Donut</a>
|
||||
</li>
|
||||
</ul>
|
||||
<section class="col-lg-6 connectedSortable">
|
||||
<div class="col-12">
|
||||
<div class="card border-0 shadow rounded-lg mb-4">
|
||||
<div class="card-header bg-primary text-white rounded-top">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-clipboard-check mr-2"></i>
|
||||
내 결재 대기 출장
|
||||
</h5>
|
||||
</div>
|
||||
</div><!-- /.card-header -->
|
||||
<div class="card-body">
|
||||
<div class="tab-content p-0">
|
||||
<!-- Morris chart - Sales -->
|
||||
<div class="chart tab-pane active" id="revenue-chart"
|
||||
style="position: relative; height: 300px;">
|
||||
<canvas id="revenue-chart-canvas" height="300" style="height: 300px;"></canvas>
|
||||
</div>
|
||||
<div class="chart tab-pane" id="sales-chart" style="position: relative; height: 300px;">
|
||||
<canvas id="sales-chart-canvas" height="300" style="height: 300px;"></canvas>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-bordered mb-0 text-center" style="border-radius: 0 0 .5rem .5rem; overflow: hidden;">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>출장일자</th>
|
||||
<th>목적지</th>
|
||||
<th>목적</th>
|
||||
<th>상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:if="${#lists.isEmpty(myApprovalslist)}">
|
||||
<td colspan="4" class="text-muted">결재할 출장 없음</td>
|
||||
</tr>
|
||||
<tr th:each="row : ${myApprovalslist}"
|
||||
th:onclick="|location.href='@{/itn/bizTrip/detail/{tripId}(tripId=${row.tripId})}'|"
|
||||
class="cursor-pointer">
|
||||
<td th:text="${row.tripDt}"></td>
|
||||
<td th:text="${row.locationTxt}"></td>
|
||||
<td th:text="${row.purpose}"></td>
|
||||
<td>
|
||||
<span class="badge"
|
||||
th:classappend="${row.status == '10'} ? ' badge-warning' :
|
||||
(${row.status == '20'} ? ' badge-info' :
|
||||
(${row.status == '30'} ? ' badge-success' : ' badge-danger'))"
|
||||
th:text="${@TCodeUtils.getCodeName('APPR_STS', row.status)}">
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div><!-- /.card-body -->
|
||||
</div>
|
||||
<!-- /.card -->
|
||||
|
||||
<!-- DIRECT CHAT -->
|
||||
<div class="card direct-chat direct-chat-primary">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Direct Chat</h3>
|
||||
|
||||
<div class="card-tools">
|
||||
<span title="3 New Messages" class="badge badge-primary">3</span>
|
||||
<button type="button" class="btn btn-tool" data-card-widget="collapse">
|
||||
<i class="fas fa-minus"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-tool" title="Contacts" data-widget="chat-pane-toggle">
|
||||
<i class="fas fa-comments"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-tool" data-card-widget="remove">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.card-header -->
|
||||
<div class="card-body">
|
||||
<!-- Conversations are loaded here -->
|
||||
<div class="direct-chat-messages">
|
||||
<!-- Message. Default to the left -->
|
||||
<div class="direct-chat-msg">
|
||||
<div class="direct-chat-infos clearfix">
|
||||
<span class="direct-chat-name float-left">Alexander Pierce</span>
|
||||
<span class="direct-chat-timestamp float-right">23 Jan 2:00 pm</span>
|
||||
</div>
|
||||
<!-- /.direct-chat-infos -->
|
||||
<img class="direct-chat-img" src="dist/img/user1-128x128.jpg" alt="message user image">
|
||||
<!-- /.direct-chat-img -->
|
||||
<div class="direct-chat-text">
|
||||
Is this template really for free? That's unbelievable!
|
||||
</div>
|
||||
<!-- /.direct-chat-text -->
|
||||
</div>
|
||||
<!-- /.direct-chat-msg -->
|
||||
|
||||
<!-- Message to the right -->
|
||||
<div class="direct-chat-msg right">
|
||||
<div class="direct-chat-infos clearfix">
|
||||
<span class="direct-chat-name float-right">Sarah Bullock</span>
|
||||
<span class="direct-chat-timestamp float-left">23 Jan 2:05 pm</span>
|
||||
</div>
|
||||
<!-- /.direct-chat-infos -->
|
||||
<img class="direct-chat-img" src="dist/img/user3-128x128.jpg" alt="message user image">
|
||||
<!-- /.direct-chat-img -->
|
||||
<div class="direct-chat-text">
|
||||
You better believe it!
|
||||
</div>
|
||||
<!-- /.direct-chat-text -->
|
||||
</div>
|
||||
<!-- /.direct-chat-msg -->
|
||||
|
||||
<!-- Message. Default to the left -->
|
||||
<div class="direct-chat-msg">
|
||||
<div class="direct-chat-infos clearfix">
|
||||
<span class="direct-chat-name float-left">Alexander Pierce</span>
|
||||
<span class="direct-chat-timestamp float-right">23 Jan 5:37 pm</span>
|
||||
</div>
|
||||
<!-- /.direct-chat-infos -->
|
||||
<img class="direct-chat-img" src="dist/img/user1-128x128.jpg" alt="message user image">
|
||||
<!-- /.direct-chat-img -->
|
||||
<div class="direct-chat-text">
|
||||
Working with AdminLTE on a great new app! Wanna join?
|
||||
</div>
|
||||
<!-- /.direct-chat-text -->
|
||||
</div>
|
||||
<!-- /.direct-chat-msg -->
|
||||
|
||||
<!-- Message to the right -->
|
||||
<div class="direct-chat-msg right">
|
||||
<div class="direct-chat-infos clearfix">
|
||||
<span class="direct-chat-name float-right">Sarah Bullock</span>
|
||||
<span class="direct-chat-timestamp float-left">23 Jan 6:10 pm</span>
|
||||
</div>
|
||||
<!-- /.direct-chat-infos -->
|
||||
<img class="direct-chat-img" src="dist/img/user3-128x128.jpg" alt="message user image">
|
||||
<!-- /.direct-chat-img -->
|
||||
<div class="direct-chat-text">
|
||||
I would love to.
|
||||
</div>
|
||||
<!-- /.direct-chat-text -->
|
||||
</div>
|
||||
<!-- /.direct-chat-msg -->
|
||||
|
||||
</div>
|
||||
<!--/.direct-chat-messages-->
|
||||
|
||||
<!-- Contacts are loaded here -->
|
||||
<div class="direct-chat-contacts">
|
||||
<ul class="contacts-list">
|
||||
<li>
|
||||
<a href="#">
|
||||
<img class="contacts-list-img" src="dist/img/user1-128x128.jpg" alt="User Avatar">
|
||||
|
||||
<div class="contacts-list-info">
|
||||
<span class="contacts-list-name">
|
||||
Count Dracula
|
||||
<small class="contacts-list-date float-right">2/28/2015</small>
|
||||
</span>
|
||||
<span class="contacts-list-msg">How have you been? I was...</span>
|
||||
</div>
|
||||
<!-- /.contacts-list-info -->
|
||||
</a>
|
||||
</li>
|
||||
<!-- End Contact Item -->
|
||||
<li>
|
||||
<a href="#">
|
||||
<img class="contacts-list-img" src="dist/img/user7-128x128.jpg" alt="User Avatar">
|
||||
|
||||
<div class="contacts-list-info">
|
||||
<span class="contacts-list-name">
|
||||
Sarah Doe
|
||||
<small class="contacts-list-date float-right">2/23/2015</small>
|
||||
</span>
|
||||
<span class="contacts-list-msg">I will be waiting for...</span>
|
||||
</div>
|
||||
<!-- /.contacts-list-info -->
|
||||
</a>
|
||||
</li>
|
||||
<!-- End Contact Item -->
|
||||
<li>
|
||||
<a href="#">
|
||||
<img class="contacts-list-img" src="dist/img/user3-128x128.jpg" alt="User Avatar">
|
||||
|
||||
<div class="contacts-list-info">
|
||||
<span class="contacts-list-name">
|
||||
Nadia Jolie
|
||||
<small class="contacts-list-date float-right">2/20/2015</small>
|
||||
</span>
|
||||
<span class="contacts-list-msg">I'll call you back at...</span>
|
||||
</div>
|
||||
<!-- /.contacts-list-info -->
|
||||
</a>
|
||||
</li>
|
||||
<!-- End Contact Item -->
|
||||
<li>
|
||||
<a href="#">
|
||||
<img class="contacts-list-img" src="dist/img/user5-128x128.jpg" alt="User Avatar">
|
||||
|
||||
<div class="contacts-list-info">
|
||||
<span class="contacts-list-name">
|
||||
Nora S. Vans
|
||||
<small class="contacts-list-date float-right">2/10/2015</small>
|
||||
</span>
|
||||
<span class="contacts-list-msg">Where is your new...</span>
|
||||
</div>
|
||||
<!-- /.contacts-list-info -->
|
||||
</a>
|
||||
</li>
|
||||
<!-- End Contact Item -->
|
||||
<li>
|
||||
<a href="#">
|
||||
<img class="contacts-list-img" src="dist/img/user6-128x128.jpg" alt="User Avatar">
|
||||
|
||||
<div class="contacts-list-info">
|
||||
<span class="contacts-list-name">
|
||||
John K.
|
||||
<small class="contacts-list-date float-right">1/27/2015</small>
|
||||
</span>
|
||||
<span class="contacts-list-msg">Can I take a look at...</span>
|
||||
</div>
|
||||
<!-- /.contacts-list-info -->
|
||||
</a>
|
||||
</li>
|
||||
<!-- End Contact Item -->
|
||||
<li>
|
||||
<a href="#">
|
||||
<img class="contacts-list-img" src="dist/img/user8-128x128.jpg" alt="User Avatar">
|
||||
|
||||
<div class="contacts-list-info">
|
||||
<span class="contacts-list-name">
|
||||
Kenneth M.
|
||||
<small class="contacts-list-date float-right">1/4/2015</small>
|
||||
</span>
|
||||
<span class="contacts-list-msg">Never mind I found...</span>
|
||||
</div>
|
||||
<!-- /.contacts-list-info -->
|
||||
</a>
|
||||
</li>
|
||||
<!-- End Contact Item -->
|
||||
</ul>
|
||||
<!-- /.contacts-list -->
|
||||
</div>
|
||||
<!-- /.direct-chat-pane -->
|
||||
</div>
|
||||
<!-- /.card-body -->
|
||||
<div class="card-footer">
|
||||
<form action="#" method="post">
|
||||
<div class="input-group">
|
||||
<input type="text" name="message" placeholder="Type Message ..." class="form-control">
|
||||
<span class="input-group-append">
|
||||
<button type="button" class="btn btn-primary">Send</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- /.card-footer-->
|
||||
</div>
|
||||
<!--/.direct-chat -->
|
||||
|
||||
<!-- TO DO List -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">
|
||||
<i class="ion ion-clipboard mr-1"></i>
|
||||
To Do List
|
||||
</h3>
|
||||
|
||||
<div class="card-tools">
|
||||
<ul class="pagination pagination-sm">
|
||||
<li class="page-item"><a href="#" class="page-link">«</a></li>
|
||||
<li class="page-item"><a href="#" class="page-link">1</a></li>
|
||||
<li class="page-item"><a href="#" class="page-link">2</a></li>
|
||||
<li class="page-item"><a href="#" class="page-link">3</a></li>
|
||||
<li class="page-item"><a href="#" class="page-link">»</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.card-header -->
|
||||
<div class="card-body">
|
||||
<ul class="todo-list" data-widget="todo-list">
|
||||
<li>
|
||||
<!-- drag handle -->
|
||||
<span class="handle">
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</span>
|
||||
<!-- checkbox -->
|
||||
<div class="icheck-primary d-inline ml-2">
|
||||
<input type="checkbox" value="" name="todo1" id="todoCheck1">
|
||||
<label for="todoCheck1"></label>
|
||||
</div>
|
||||
<!-- todo text -->
|
||||
<span class="text">Design a nice theme</span>
|
||||
<!-- Emphasis label -->
|
||||
<small class="badge badge-danger"><i class="far fa-clock"></i> 2 mins</small>
|
||||
<!-- General tools such as edit or delete-->
|
||||
<div class="tools">
|
||||
<i class="fas fa-edit"></i>
|
||||
<i class="fas fa-trash-o"></i>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<span class="handle">
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</span>
|
||||
<div class="icheck-primary d-inline ml-2">
|
||||
<input type="checkbox" value="" name="todo2" id="todoCheck2" checked>
|
||||
<label for="todoCheck2"></label>
|
||||
</div>
|
||||
<span class="text">Make the theme responsive</span>
|
||||
<small class="badge badge-info"><i class="far fa-clock"></i> 4 hours</small>
|
||||
<div class="tools">
|
||||
<i class="fas fa-edit"></i>
|
||||
<i class="fas fa-trash-o"></i>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<span class="handle">
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</span>
|
||||
<div class="icheck-primary d-inline ml-2">
|
||||
<input type="checkbox" value="" name="todo3" id="todoCheck3">
|
||||
<label for="todoCheck3"></label>
|
||||
</div>
|
||||
<span class="text">Let theme shine like a star</span>
|
||||
<small class="badge badge-warning"><i class="far fa-clock"></i> 1 day</small>
|
||||
<div class="tools">
|
||||
<i class="fas fa-edit"></i>
|
||||
<i class="fas fa-trash-o"></i>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<span class="handle">
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</span>
|
||||
<div class="icheck-primary d-inline ml-2">
|
||||
<input type="checkbox" value="" name="todo4" id="todoCheck4">
|
||||
<label for="todoCheck4"></label>
|
||||
</div>
|
||||
<span class="text">Let theme shine like a star</span>
|
||||
<small class="badge badge-success"><i class="far fa-clock"></i> 3 days</small>
|
||||
<div class="tools">
|
||||
<i class="fas fa-edit"></i>
|
||||
<i class="fas fa-trash-o"></i>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<span class="handle">
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</span>
|
||||
<div class="icheck-primary d-inline ml-2">
|
||||
<input type="checkbox" value="" name="todo5" id="todoCheck5">
|
||||
<label for="todoCheck5"></label>
|
||||
</div>
|
||||
<span class="text">Check your messages and notifications</span>
|
||||
<small class="badge badge-primary"><i class="far fa-clock"></i> 1 week</small>
|
||||
<div class="tools">
|
||||
<i class="fas fa-edit"></i>
|
||||
<i class="fas fa-trash-o"></i>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<span class="handle">
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</span>
|
||||
<div class="icheck-primary d-inline ml-2">
|
||||
<input type="checkbox" value="" name="todo6" id="todoCheck6">
|
||||
<label for="todoCheck6"></label>
|
||||
</div>
|
||||
<span class="text">Let theme shine like a star</span>
|
||||
<small class="badge badge-secondary"><i class="far fa-clock"></i> 1 month</small>
|
||||
<div class="tools">
|
||||
<i class="fas fa-edit"></i>
|
||||
<i class="fas fa-trash-o"></i>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- /.card-body -->
|
||||
<div class="card-footer clearfix">
|
||||
<button type="button" class="btn btn-primary float-right"><i class="fas fa-plus"></i> Add item</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.card -->
|
||||
</section>
|
||||
<!-- /.Left col -->
|
||||
<!-- right col (We are only adding the ID to make the widgets sortable)-->
|
||||
<section class="col-lg-5 connectedSortable">
|
||||
|
||||
<!-- Map card -->
|
||||
<div class="card bg-gradient-primary">
|
||||
<div class="card-header border-0">
|
||||
<h3 class="card-title">
|
||||
<i class="fas fa-map-marker-alt mr-1"></i>
|
||||
Visitors
|
||||
</h3>
|
||||
<!-- card tools -->
|
||||
<div class="card-tools">
|
||||
<button type="button" class="btn btn-primary btn-sm daterange" title="Date range">
|
||||
<i class="far fa-calendar-alt"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary btn-sm" data-card-widget="collapse" title="Collapse">
|
||||
<i class="fas fa-minus"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- /.card-tools -->
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="world-map" style="height: 250px; width: 100%;"></div>
|
||||
</div>
|
||||
<!-- /.card-body-->
|
||||
<div class="card-footer bg-transparent">
|
||||
<div class="row">
|
||||
<div class="col-4 text-center">
|
||||
<div id="sparkline-1"></div>
|
||||
<div class="text-white">Visitors</div>
|
||||
</div>
|
||||
<!-- ./col -->
|
||||
<div class="col-4 text-center">
|
||||
<div id="sparkline-2"></div>
|
||||
<div class="text-white">Online</div>
|
||||
</div>
|
||||
<!-- ./col -->
|
||||
<div class="col-4 text-center">
|
||||
<div id="sparkline-3"></div>
|
||||
<div class="text-white">Sales</div>
|
||||
</div>
|
||||
<!-- ./col -->
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.card -->
|
||||
|
||||
<!-- solid sales graph -->
|
||||
<div class="card bg-gradient-info">
|
||||
<div class="card-header border-0">
|
||||
<h3 class="card-title">
|
||||
<i class="fas fa-th mr-1"></i>
|
||||
Sales Graph
|
||||
</h3>
|
||||
|
||||
<div class="card-tools">
|
||||
<button type="button" class="btn bg-info btn-sm" data-card-widget="collapse">
|
||||
<i class="fas fa-minus"></i>
|
||||
</button>
|
||||
<button type="button" class="btn bg-info btn-sm" data-card-widget="remove">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas class="chart" id="line-chart" style="min-height: 250px; height: 250px; max-height: 250px; max-width: 100%;"></canvas>
|
||||
</div>
|
||||
<!-- /.card-body -->
|
||||
<div class="card-footer bg-transparent">
|
||||
<div class="row">
|
||||
<div class="col-4 text-center">
|
||||
<input type="text" class="knob" data-readonly="true" value="20" data-width="60" data-height="60"
|
||||
data-fgColor="#39CCCC">
|
||||
|
||||
<div class="text-white">Mail-Orders</div>
|
||||
</div>
|
||||
<!-- ./col -->
|
||||
<div class="col-4 text-center">
|
||||
<input type="text" class="knob" data-readonly="true" value="50" data-width="60" data-height="60"
|
||||
data-fgColor="#39CCCC">
|
||||
|
||||
<div class="text-white">Online</div>
|
||||
</div>
|
||||
<!-- ./col -->
|
||||
<div class="col-4 text-center">
|
||||
<input type="text" class="knob" data-readonly="true" value="30" data-width="60" data-height="60"
|
||||
data-fgColor="#39CCCC">
|
||||
|
||||
<div class="text-white">In-Store</div>
|
||||
</div>
|
||||
<!-- ./col -->
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
</div>
|
||||
<!-- /.card-footer -->
|
||||
</div>
|
||||
<!-- /.card -->
|
||||
|
||||
<!-- Calendar -->
|
||||
<div class="card bg-gradient-success">
|
||||
<div class="card-header border-0">
|
||||
|
||||
<h3 class="card-title">
|
||||
<i class="far fa-calendar-alt"></i>
|
||||
Calendar
|
||||
</h3>
|
||||
<!-- tools card -->
|
||||
<div class="card-tools">
|
||||
<!-- button with a dropdown -->
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-success btn-sm dropdown-toggle" data-toggle="dropdown" data-offset="-52">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu" role="menu">
|
||||
<a href="#" class="dropdown-item">Add new event</a>
|
||||
<a href="#" class="dropdown-item">Clear events</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a href="#" class="dropdown-item">View calendar</a>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-success btn-sm" data-card-widget="collapse">
|
||||
<i class="fas fa-minus"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-success btn-sm" data-card-widget="remove">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- /. tools -->
|
||||
</div>
|
||||
<!-- /.card-header -->
|
||||
<div class="card-body pt-0">
|
||||
<!--The calendar -->
|
||||
<div id="calendar" style="width: 100%"></div>
|
||||
</div>
|
||||
<!-- /.card-body -->
|
||||
</div>
|
||||
<!-- /.card -->
|
||||
</section>
|
||||
<!-- right col -->
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head th:fragment="headerFragment">
|
||||
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
||||
<th:block th:fragment="headerFragment">
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
@ -37,6 +37,16 @@
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" th:href="@{/plugins/toastr/toastr.min.css}">
|
||||
|
||||
|
||||
<link rel="stylesheet" th:href="@{/plugins/datatables-bs4/css/dataTables.bootstrap4.min.css}">
|
||||
<link rel="stylesheet" th:href="@{/plugins/datatables-responsive/css/responsive.bootstrap4.min.css}">
|
||||
<link rel="stylesheet" th:href="@{/plugins/datatables-buttons/css/buttons.bootstrap4.min.css}">
|
||||
<!-- SweetAlert2 CSS -->
|
||||
<link rel="stylesheet" th:href="@{https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css}" >
|
||||
|
||||
|
||||
|
||||
<!-- CSS -->
|
||||
|
||||
|
||||
@ -78,6 +88,8 @@
|
||||
|
||||
<script th:src="@{/plugins/toastr/toastr.min.js}"></script>
|
||||
|
||||
<!-- SweetAlert2 JS -->
|
||||
<script th:src="@{https://cdn.jsdelivr.net/npm/sweetalert2@11}"></script>
|
||||
|
||||
|
||||
<script>
|
||||
@ -133,5 +145,6 @@
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<!-- 개별 페이지에서 스타일/스크립트 넣을 수 있게 확장 지점 추가 -->
|
||||
</th:block>
|
||||
</html>
|
||||
@ -92,11 +92,11 @@
|
||||
</a>
|
||||
<ul class="nav nav-treeview">
|
||||
<li class="nav-item">
|
||||
<a th:href="@{/itn/bizTrip/reg}" class="nav-link">
|
||||
<a th:href="@{/itn/bizTrip/list}" class="nav-link">
|
||||
<!-- <i class="far fa-circle nav-icon"></i>-->
|
||||
<!-- <i class="far fa-clock nav-icon"></i>-->
|
||||
<i class="nav-icon fas fa-calendar-check"></i>
|
||||
<p>출장 등록</p>
|
||||
<p>출장</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@ -136,12 +136,22 @@
|
||||
<i class="fas fa-expand-arrows-alt"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-widget="control-sidebar" data-controlsidebar-slide="true" href="#" role="button">
|
||||
<i class="fas fa-th-large"></i>
|
||||
</a>
|
||||
<!-- <li class="nav-item">-->
|
||||
<!-- <a class="nav-link" data-widget="control-sidebar" data-controlsidebar-slide="true" href="#" role="button">-->
|
||||
<!-- <i class="fas fa-th-large"></i>-->
|
||||
<!-- </a>-->
|
||||
<!-- </li>-->
|
||||
|
||||
<li class="nav-item d-none d-sm-inline-block">
|
||||
<span class="nav-link">
|
||||
<i class="fas fa-user-circle text-primary mr-1 fs-3"></i>
|
||||
<strong class="text-dark fs-3 fw-semibold"
|
||||
th:text="${session.loginVO.userName}">
|
||||
사용자</strong>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
|
||||
<li class="nav-item d-none d-sm-inline-block">
|
||||
<a th:href="@{/logout}" class="nav-link">logout</a>
|
||||
</li>
|
||||
|
||||
334
src/main/resources/templates/itn/bizTrip/edit.html
Normal file
334
src/main/resources/templates/itn/bizTrip/edit.html
Normal file
@ -0,0 +1,334 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="layout">
|
||||
<head>
|
||||
<th:block layout:fragment="title">
|
||||
<title>출장 수정</title>
|
||||
</th:block>
|
||||
|
||||
<th:block layout:fragment="head">
|
||||
<style>
|
||||
.table-form th {
|
||||
background-color: #f1f1f1;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
padding: 10px;
|
||||
width: 15%;
|
||||
}
|
||||
.table-form td {
|
||||
padding: 10px;
|
||||
}
|
||||
.card-header {
|
||||
background-color: #ffffff;
|
||||
font-weight: bold;
|
||||
border-top: 2px solid #009fe3;
|
||||
}
|
||||
</style>
|
||||
</th:block>
|
||||
</head>
|
||||
|
||||
<body layout:fragment="body">
|
||||
<div class="wrapper">
|
||||
<div th:replace="~{fragments/top_nav :: topFragment}"/>
|
||||
<aside class="main-sidebar sidebar-dark-primary elevation-4"
|
||||
th:insert="~{fragments/mainsidebar :: sidebarFragment}"></aside>
|
||||
<div class="content-wrapper">
|
||||
<div class="content-header">
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">
|
||||
<h1 class="m-0">출장 수정</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">출장정보</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-bordered table-form" style="table-layout: fixed;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>출장구분</th>
|
||||
<td>
|
||||
<select class="form-control" id="tripType" name="tripType">
|
||||
<option value="">-- 선택 --</option>
|
||||
<option th:each="code : ${@TCodeUtils.getCodeList('TRIP_TYPE')}"
|
||||
th:value="${code.codeId}"
|
||||
th:selected="${trip.tripTypeCd == code.codeId}"
|
||||
th:text="${code.codeName}">
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
<th>출장지</th>
|
||||
<td>
|
||||
<select class="form-control d-inline" style="width: 20%;" id="tripLocation">
|
||||
<option value="">-- 지역 --</option>
|
||||
<option th:each="code : ${@TCodeUtils.getCodeList('TRIP_LOCATION')}"
|
||||
th:value="${code.codeId}"
|
||||
th:selected="${trip.locationCd == code.codeId}"
|
||||
th:text="${code.codeName}">
|
||||
</option>
|
||||
</select>
|
||||
<input type="text" id="locationTxt" class="form-control d-inline" style="width: 70%;" th:value="${trip.locationTxt}" placeholder="목적지"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>출장목적</th>
|
||||
<td><input type="text" id="purpose" class="form-control" th:value="${trip.purpose}"/></td>
|
||||
<th>이동사항</th>
|
||||
<td>
|
||||
<select class="form-control" id="tripMove">
|
||||
<option value="">-- 선택 --</option>
|
||||
<option th:each="code : ${@TCodeUtils.getCodeList('TRIP_MOVE')}"
|
||||
th:value="${code.codeId}"
|
||||
th:selected="${trip.moveCd == code.codeId}"
|
||||
th:text="${code.codeName}">
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>출장일자</th>
|
||||
<td><input type="date" id="tripDate" class="form-control" th:value="${#temporals.format(trip.tripDt, 'yyyy-MM-dd')}"/></td>
|
||||
<th>시간</th>
|
||||
<td>
|
||||
<div class="d-flex">
|
||||
<div class="input-group date" id="startTimePicker" data-target-input="nearest" style="margin-right: 5px;">
|
||||
<input type="text"
|
||||
class="form-control datetimepicker-input"
|
||||
data-target="#startTimePicker"
|
||||
data-toggle="datetimepicker"
|
||||
th:value="${trip.startTime}"/>
|
||||
<div class="input-group-append" data-target="#startTimePicker">
|
||||
<div class="input-group-text"><i class="far fa-clock"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="align-self-center mx-2">~</span>
|
||||
<div class="input-group date" id="endTimePicker" data-target-input="nearest">
|
||||
<input type="text"
|
||||
class="form-control datetimepicker-input"
|
||||
data-target="#endTimePicker"
|
||||
data-toggle="datetimepicker"
|
||||
th:value="${trip.endTime}"/>
|
||||
<div class="input-group-append" data-target="#endTimePicker">
|
||||
<div class="input-group-text"><i class="far fa-clock"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- 출장 인원 영역/결재라인은 reg.html 복사 후 바인딩만 유지 -->
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">출장 인원</div>
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-bordered table-form mb-0">
|
||||
<thead>
|
||||
<tr class="text-center bg-light">
|
||||
<th>이름</th>
|
||||
<th>부서명</th>
|
||||
<th>연락처</th>
|
||||
<th style="width: 15%;" class="text-center">
|
||||
<button type="button" class="btn btn-info btn-sm" data-toggle="modal" data-target="#userSearchModal">
|
||||
<i class="fas fa-user-plus"></i> 인원 추가
|
||||
</button>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tripMemberTbody">
|
||||
<tr th:each="member : ${trip.memberList}"
|
||||
th:data-uniqid="${member.uniqId}">
|
||||
<td th:text="${@TCodeUtils.getUserName(member.uniqId)}"/>
|
||||
<td th:text="${@TCodeUtils.getUserDeptTxt(member.uniqId)}"/>
|
||||
<td th:text="${@TCodeUtils.getUserMobilePhone(member.uniqId)}"/>
|
||||
<td class="text-center"
|
||||
th:if="${member.role == '0'}"
|
||||
th:text="${member.role == '0' ? '기안자' : ''}"/>
|
||||
|
||||
<td class="text-center"
|
||||
th:unless="${member.role == '0'}">
|
||||
<a href="#" class="btn btn-danger btn-sm" onclick="removeMemberRow(this)">
|
||||
<i class="fas fa-user-minus"></i> 삭제
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 결제 라인 영역 -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
결제라인
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-bordered table-form mb-0">
|
||||
<thead>
|
||||
<tr class="text-center bg-light">
|
||||
<th style="width: 20%;">결재 단계</th>
|
||||
<th style="width: 20%;">이름</th>
|
||||
<th style="width: 25%;">부서명</th>
|
||||
<th style="width: 25%;">연락처</th>
|
||||
<th style="width: 10%;" class="text-center">관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="approvalLineTbody">
|
||||
<tr th:each="line, iterStat : ${trip.approvalList}">
|
||||
<td class="text-center align-middle"
|
||||
th:text="${line.orderNo == 3 ? '결재' : '검토 ' + line.orderNo}">결재 단계</td>
|
||||
|
||||
<th:block th:if="${line.approverId != null}">
|
||||
<td th:text="${@TCodeUtils.getUserName(line.approverId)}">이름</td>
|
||||
<td th:text="${@TCodeUtils.getUserDeptTxt(line.approverId)}">부서</td>
|
||||
<td th:text="${@TCodeUtils.getUserMobilePhone(line.approverId)}">연락처</td>
|
||||
<td class="text-center">
|
||||
<a href="#" class="btn btn-danger btn-sm" th:onclick="|resetApproval('approval${line.orderNo}')|">
|
||||
<i class="fas fa-user-minus"></i> 삭제
|
||||
</a>
|
||||
</td>
|
||||
<!-- 히든값 설정 -->
|
||||
<input type="hidden" th:id="'approver' + ${line.orderNo}"
|
||||
th:name="'approver' + ${line.orderNo}" th:value="${line.approverId}" />
|
||||
</th:block>
|
||||
|
||||
<th:block th:if="${line.approverId == null}">
|
||||
<td colspan="4">
|
||||
<button type="button" class="btn btn-outline-info btn-sm"
|
||||
th:onclick="|openApprovalModal('approval${line.orderNo}')|">
|
||||
<i class="fas fa-user-plus"></i> 사용자 지정
|
||||
</button>
|
||||
</td>
|
||||
</th:block>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-right mt-2">
|
||||
<button type="submit" class="btn btn-primary btn-sm" th:onclick="updateTripData([[${trip.tripId}]]);">
|
||||
<i class="fas fa-save"></i> 수정
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div> <!-- 사용자 검색 모달 -->
|
||||
<div class="modal fade" id="approvalSearchModal" tabindex="-1" role="dialog" aria-labelledby="userSearchModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-info">
|
||||
<h5 class="modal-title text-white" id="approvalSearchModalLabel">사용자 검색</h5>
|
||||
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<!-- 검색 영역 -->
|
||||
<div class="form-inline mb-3">
|
||||
<label class="mr-2">이름 검색</label>
|
||||
<input type="text" class="form-control mr-2" auto id="approvalSearchKeyword" placeholder="검색어 입력" autocomplete="off">
|
||||
<button type="button" class="btn btn-info btn-sm" onclick="searchUser()">검색</button>
|
||||
</div>
|
||||
|
||||
<!-- 결과 테이블 -->
|
||||
<div class="table-responsive" style="max-height: 500px; overflow-y: auto;">
|
||||
<table class="table table-hover table-bordered table-sm text-center">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>이름</th>
|
||||
<th>부서</th>
|
||||
<th>직급</th>
|
||||
<th>추가</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="approvalSearchResult">
|
||||
<!-- 검색 결과 동적 렌더링 -->
|
||||
<!-- 예시 -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">닫기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 사용자 검색 모달 -->
|
||||
<div class="modal fade" id="userSearchModal" tabindex="-1" role="dialog" aria-labelledby="userSearchModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-info">
|
||||
<h5 class="modal-title text-white" id="userSearchModalLabel">사용자 검색</h5>
|
||||
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<!-- 검색 영역 -->
|
||||
<div class="form-inline mb-3">
|
||||
<label class="mr-2">이름 검색</label>
|
||||
<input type="text" class="form-control mr-2" auto id="userSearchKeyword" placeholder="검색어 입력" autocomplete="off">
|
||||
<button type="button" class="btn btn-info btn-sm" onclick="searchUser()">검색</button>
|
||||
</div>
|
||||
|
||||
<!-- 결과 테이블 -->
|
||||
<div class="table-responsive" style="max-height: 500px; overflow-y: auto;">
|
||||
<table class="table table-hover table-bordered table-sm text-center">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>이름</th>
|
||||
<th>부서</th>
|
||||
<th>직급</th>
|
||||
<th>추가</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="userSearchResult">
|
||||
<!-- 검색 결과 동적 렌더링 -->
|
||||
<!-- 예시 -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">닫기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- /.content-wrapper -->
|
||||
<footer class="main-footer"
|
||||
th:insert="~{fragments/footer :: footerFragment}">
|
||||
</footer>
|
||||
|
||||
<!-- Control Sidebar -->
|
||||
<aside class="control-sidebar control-sidebar-dark">
|
||||
<!-- Control sidebar content goes here -->
|
||||
</aside>
|
||||
<!-- /.control-sidebar -->
|
||||
</div>
|
||||
<!-- ./wrapper -->
|
||||
|
||||
<script th:src="@{/cmn/js/bizTrip/edit/init.js}"></script>
|
||||
<script th:src="@{/cmn/js/bizTrip/edit/event.js}"></script>
|
||||
<script th:src="@{/cmn/js/bizTrip/edit/service.js}"></script>
|
||||
<script th:src="@{/cmn/js/bizTrip/edit/validation.js}"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -5,15 +5,13 @@
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="layout">
|
||||
<head>
|
||||
<!-- layout.html 에 들어간 head 부분을 제외하고 개별 파일에만 적용되는 head 부분 추가 -->
|
||||
<title>사용자 관리</title>
|
||||
|
||||
<!-- 필요하다면 개별 파일에 사용될 css/js 선언 -->
|
||||
|
||||
<link rel="stylesheet" th:href="@{/plugins/datatables-bs4/css/dataTables.bootstrap4.min.css}">
|
||||
<link rel="stylesheet" th:href="@{/plugins/datatables-responsive/css/responsive.bootstrap4.min.css}">
|
||||
<link rel="stylesheet" th:href="@{/plugins/datatables-buttons/css/buttons.bootstrap4.min.css}">
|
||||
<th:block layout:fragment="title">
|
||||
<title>출장 목록</title>
|
||||
</th:block>
|
||||
|
||||
<th:block layout:fragment="head">
|
||||
<style>
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
@ -23,6 +21,7 @@
|
||||
color: #007bff;
|
||||
}
|
||||
</style>
|
||||
</th:block>
|
||||
</head>
|
||||
|
||||
<body layout:fragment="body">
|
||||
@ -42,12 +41,12 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">
|
||||
<h1 class="m-0">사용자 관리</h1>
|
||||
<h1 class="m-0">출장 목록</h1>
|
||||
</div><!-- /.col -->
|
||||
<div class="col-sm-6">
|
||||
<ol class="breadcrumb float-sm-right">
|
||||
<li class="breadcrumb-item"><a href="#">Home</a></li>
|
||||
<li class="breadcrumb-item active">출퇴근 관리</li>
|
||||
<li class="breadcrumb-item active">출장 목록</li>
|
||||
</ol>
|
||||
</div><!-- /.col -->
|
||||
</div><!-- /.row -->
|
||||
@ -80,46 +79,54 @@
|
||||
<th>목적</th>
|
||||
<th>이동수단</th>
|
||||
<th>상태</th>
|
||||
<th>결재상태</th>
|
||||
<th>작성자</th>
|
||||
<!-- <th>결재상태</th>-->
|
||||
<th>결재대기자</th>
|
||||
<th>기안자</th>
|
||||
<th style="width: 100px;">관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="row, stat : ${list}">
|
||||
<td th:text="${stat.count}"/>
|
||||
<td th:text="${row.tripId}"/>
|
||||
<td th:text="${#temporals.format(row.tripDt, 'yyyy-MM-dd')}"/>
|
||||
<td th:text="${row.startTime + ' ~ ' + row.endTime}"/>
|
||||
<td th:text="${@TCodeUtils.getCodeName('TRIP_TYPE', row.tripTypeCd)}"/>
|
||||
<td th:text="${@TCodeUtils.getCodeName('TRIP_LOCATION', row.locationCd)}"/>
|
||||
<td th:text="${row.locationTxt}"/>
|
||||
<td th:text="${row.purpose}"/>
|
||||
<td th:text="${@TCodeUtils.getCodeName('TRIP_MOVE', row.moveCd)}"/>
|
||||
<td>
|
||||
<span th:switch="${row.status}">
|
||||
<span th:case="'10'" class="badge badge-warning">대기</span>
|
||||
<span th:case="'20'" class="badge badge-primary">진행</span>
|
||||
<span th:case="'30'" class="badge badge-success">승인</span>
|
||||
<span th:case="'40'" class="badge badge-danger">반려</span>
|
||||
<span th:case="*">-</span>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span th:switch="${row.latestApproveStatus}">
|
||||
<span th:case="'10'" class="badge badge-warning">대기</span>
|
||||
<span th:case="'20'" class="badge badge-primary">진행</span>
|
||||
<span th:case="'30'" class="badge badge-success">승인</span>
|
||||
<span th:case="'40'" class="badge badge-danger">반려</span>
|
||||
<span th:case="*">-</span>
|
||||
</span>
|
||||
</td>
|
||||
<td th:text="${row.frstRegisterId}"/>
|
||||
</tr>
|
||||
<tr th:each="row, stat : ${list}"
|
||||
th:onclick="|location.href='@{/itn/bizTrip/detail/{tripId}(tripId=${row.tripId})}'|"
|
||||
class="cursor-pointer">
|
||||
<td th:text="${stat.count}"/>
|
||||
<td th:text="${row.tripId}"/>
|
||||
<td th:text="${#temporals.format(row.tripDt, 'yyyy-MM-dd')}"/>
|
||||
<td th:text="${row.startTime + ' ~ ' + row.endTime}"/>
|
||||
<td th:text="${@TCodeUtils.getCodeName('TRIP_TYPE', row.tripTypeCd)}"/>
|
||||
<td th:text="${@TCodeUtils.getCodeName('TRIP_LOCATION', row.locationCd)}"/>
|
||||
<td th:text="${row.locationTxt}"/>
|
||||
<td th:text="${row.purpose}"/>
|
||||
<td th:text="${@TCodeUtils.getCodeName('TRIP_MOVE', row.moveCd)}"/>
|
||||
<td>
|
||||
<span th:switch="${row.status}">
|
||||
<span th:case="'10'" class="badge badge-warning px-3 py-2 fs-6">대기</span>
|
||||
<span th:case="'20'" class="badge badge-primary px-3 py-2 fs-6">진행</span>
|
||||
<span th:case="'30'" class="badge badge-success px-3 py-2 fs-6">승인</span>
|
||||
<span th:case="'40'" class="badge badge-danger px-3 py-2 fs-6">반려</span>
|
||||
<span th:case="*">-</span>
|
||||
</span>
|
||||
</td>
|
||||
<td th:if="${row.status=='10' or row.status=='20'}" th:text="${@TCodeUtils.getUserName(row.currentApproverId)}"/>
|
||||
<td th:unless="${row.status=='10' or row.status=='20'}" >-</td>
|
||||
<td th:text="${@TCodeUtils.getUserName(row.frstRegisterId)}"/>
|
||||
<td class="text-center">
|
||||
<div th:if="${row.status == '10' and (row.frstRegisterId == loginUser.uniqId or loginUser.role.name() == 'ROLE_ADMIN')}">
|
||||
<a th:href="@{/itn/bizTrip/edit/{tripId}(tripId=${row.tripId})}" class="btn btn-sm btn-warning">수정</a>
|
||||
<button type="button" class="btn btn-sm btn-danger" th:onclick="|deleteTrip('${row.tripId}')|">삭제</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<!-- /.card-body -->
|
||||
<div class="card-footer text-right">
|
||||
<a href="/itn/bizTrip/reg" class="btn btn-primary">
|
||||
<i class="fas fa-plus"></i> 출장 등록
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.card -->
|
||||
</div>
|
||||
@ -207,7 +214,7 @@
|
||||
exportOptions: commonExportOptions
|
||||
},
|
||||
"colvis"]
|
||||
}).buttons().container().appendTo('#commuteTb_wrapper .col-md-6:eq(0)');
|
||||
}).buttons().container().appendTo('#tripTb_wrapper .col-md-6:eq(0)');
|
||||
|
||||
|
||||
|
||||
|
||||
@ -10,9 +10,12 @@
|
||||
|
||||
<!-- 필요하다면 개별 파일에 사용될 css/js 선언 -->
|
||||
|
||||
<link rel="stylesheet" th:href="@{/plugins/datatables-bs4/css/dataTables.bootstrap4.min.css}">
|
||||
<link rel="stylesheet" th:href="@{/plugins/datatables-responsive/css/responsive.bootstrap4.min.css}">
|
||||
<link rel="stylesheet" th:href="@{/plugins/datatables-buttons/css/buttons.bootstrap4.min.css}">
|
||||
<th:block layout:fragment="title">
|
||||
<title>출장 등록</title>
|
||||
</th:block>
|
||||
|
||||
<th:block layout:fragment="head">
|
||||
|
||||
|
||||
<style>
|
||||
.table-form th {
|
||||
@ -34,6 +37,7 @@
|
||||
}
|
||||
|
||||
</style>
|
||||
</th:block>
|
||||
</head>
|
||||
|
||||
<body layout:fragment="body">
|
||||
@ -68,13 +72,8 @@
|
||||
<!-- Main content -->
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<!-- <h3>출장 등록</h3>-->
|
||||
|
||||
<!-- 출장신청 영역 -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
출장정보
|
||||
</div>
|
||||
<div class="card-header">출장정보</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-bordered table-form" style="table-layout: fixed;">
|
||||
<tbody>
|
||||
@ -146,7 +145,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@ -352,10 +351,10 @@
|
||||
</div>
|
||||
<!-- ./wrapper -->
|
||||
|
||||
<script th:src="@{/cmn/js/bizTrip/init.js}"></script>
|
||||
<script th:src="@{/cmn/js/bizTrip/event.js}"></script>
|
||||
<script th:src="@{/cmn/js/bizTrip/service.js}"></script>
|
||||
<script th:src="@{/cmn/js/bizTrip/validation.js}"></script>
|
||||
<script th:src="@{/cmn/js/bizTrip/reg/init.js}"></script>
|
||||
<script th:src="@{/cmn/js/bizTrip/reg/event.js}"></script>
|
||||
<script th:src="@{/cmn/js/bizTrip/reg/service.js}"></script>
|
||||
<script th:src="@{/cmn/js/bizTrip/reg/validation.js}"></script>
|
||||
</body>
|
||||
|
||||
|
||||
|
||||
260
src/main/resources/templates/itn/bizTrip/tripDetail.html
Normal file
260
src/main/resources/templates/itn/bizTrip/tripDetail.html
Normal file
@ -0,0 +1,260 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="layout">
|
||||
<head>
|
||||
|
||||
<th:block layout:fragment="title">
|
||||
<title>출장 상세</title>
|
||||
</th:block>
|
||||
|
||||
<th:block layout:fragment="head">
|
||||
<style>
|
||||
.table-form th {
|
||||
background-color: #f1f1f1;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
padding: 10px;
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
.table-form td {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background-color: #ffffff;
|
||||
font-weight: bold;
|
||||
border-top: 2px solid #009fe3;
|
||||
}
|
||||
|
||||
.approve-date {
|
||||
font-size: 0.85rem;
|
||||
margin-left: 10px;
|
||||
color: #6c757d;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
.approve-date i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</th:block>
|
||||
</head>
|
||||
|
||||
<body layout:fragment="body">
|
||||
<div class="wrapper">
|
||||
<div th:replace="~{fragments/top_nav :: topFragment}"/>
|
||||
<aside class="main-sidebar sidebar-dark-primary elevation-4"
|
||||
th:insert="~{fragments/mainsidebar :: sidebarFragment}"></aside>
|
||||
<div class="content-wrapper">
|
||||
<div class="content-header">
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">
|
||||
<h1 class="m-0">출장 상세</h1>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<ol class="breadcrumb float-sm-right">
|
||||
<li class="breadcrumb-item"><a href="#">Home</a></li>
|
||||
<li class="breadcrumb-item active">출장</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">출장정보</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-bordered table-form" style="table-layout: fixed;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>출장구분</th>
|
||||
<td th:text="${@TCodeUtils.getCodeName('TRIP_TYPE', trip.tripTypeCd)}"></td>
|
||||
<th>출장지</th>
|
||||
<td>
|
||||
<span th:text="${@TCodeUtils.getCodeName('TRIP_LOCATION', trip.locationCd)}"/> -
|
||||
<span th:text="${trip.locationTxt}"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>출장목적</th>
|
||||
<td th:text="${trip.purpose}"/>
|
||||
<th>이동사항</th>
|
||||
<td th:text="${@TCodeUtils.getCodeName('TRIP_MOVE', trip.moveCd)}"/>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>출장일자</th>
|
||||
<td th:text="${#temporals.format(trip.tripDt, 'yyyy-MM-dd')}"/>
|
||||
<th>시간</th>
|
||||
<td th:text="${trip.startTime + ' ~ ' + trip.endTime}"/>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">출장 인원</div>
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-bordered table-form mb-0">
|
||||
<thead>
|
||||
<tr class="text-center bg-light">
|
||||
<th>이름</th>
|
||||
<th>부서명</th>
|
||||
<th>연락처</th>
|
||||
<th>역할</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="member : ${trip.memberList}">
|
||||
<td th:text="${@TCodeUtils.getUserName(member.uniqId)}"/>
|
||||
<td th:text="${@TCodeUtils.getUserDeptTxt(member.uniqId)}"/>
|
||||
<td th:text="${@TCodeUtils.getUserMobilePhone(member.uniqId)}"/>
|
||||
<td class="text-center"
|
||||
th:text="${member.role == '0' ? '기안자' : ''}">
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">결제라인</div>
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-bordered table-form mb-0">
|
||||
<thead>
|
||||
<tr class="text-center bg-light">
|
||||
<th>결재 단계</th>
|
||||
<th>이름</th>
|
||||
<th>부서명(직급)</th>
|
||||
<th>연락처</th>
|
||||
<th>상태</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="approval, stat : ${trip.approvalList}"
|
||||
th:classappend="${approval.approverId == firstWaitingApproverId} ? 'table-warning'">
|
||||
<td th:if="${stat.last}" class="text-success font-weight-bold">결제</td>
|
||||
<td th:unless="${stat.last}" class="text-primary">검토</td>
|
||||
<td th:text="${@TCodeUtils.getUserName(approval.approverId)}"/>
|
||||
<td th:text="${@TCodeUtils.getUserDeptTxt(approval.approverId) + ' ('+@TCodeUtils.getUserRankTxt(approval.approverId)+')'}"/>
|
||||
<td th:text="${@TCodeUtils.getUserMobilePhone(approval.approverId)}"/>
|
||||
<td>
|
||||
<!-- 대기 상태 - 본인 아님 -->
|
||||
<span th:if="${approval.approveStatus == '10' and loginUser.uniqId != approval.approverId}"
|
||||
class="badge badge-warning px-3 py-2 fs-6">대기</span>
|
||||
|
||||
<!-- 대기 상태 - 본인일 때 버튼 -->
|
||||
<div th:if="${approval.approveStatus == '10' and loginUser.uniqId == approval.approverId}">
|
||||
<button class="btn btn-sm btn-success"
|
||||
th:attr="data-id=${approval.id}, data-approver=${approval.approverId}, data-trip-id=${trip.tripId} "
|
||||
onclick="handleApprove(this, 30)">승인
|
||||
</button>
|
||||
|
||||
<button class="btn btn-sm btn-danger"
|
||||
th:attr="data-id=${approval.id}, data-approver=${approval.approverId}, data-trip-id=${trip.tripId} "
|
||||
onclick="handleApprove(this, 40)">반려
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 승인 상태 -->
|
||||
<div th:if="${approval.approveStatus == '30'}">
|
||||
<span class="badge badge-success px-3 py-2 fs-6">승인</span>
|
||||
<small class="approve-date">
|
||||
<i class="far fa-clock"></i>
|
||||
<span th:text="${#dates.format(approval.approveDt, 'yyyy-MM-dd HH:mm')}"></span>
|
||||
</small>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 반려 상태 -->
|
||||
<div th:if="${approval.approveStatus == '40'}">
|
||||
<span class="badge badge-danger px-3 py-2 fs-6">반려</span>
|
||||
<small class="approve-date">
|
||||
<i class="far fa-clock"></i>
|
||||
<span th:text="${#dates.format(approval.approveDt, 'yyyy-MM-dd HH:mm')}"></span>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- 예외 상태 -->
|
||||
<span th:if="${approval.approveStatus != '10' and approval.approveStatus != '30' and approval.approveStatus != '40'}"
|
||||
class="badge badge-secondary px-3 py-2 fs-6">-</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer text-right">
|
||||
<a href="/itn/bizTrip/list" class="btn btn-secondary">
|
||||
<i class="fas fa-list"></i> 목록
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
function handleApprove(btn, approveStatus) {
|
||||
const id = btn.dataset.id;
|
||||
const approverId = btn.dataset.approver;
|
||||
const tripId = btn.dataset.tripId;
|
||||
|
||||
console.log("id : ", id, ", approverId : ", approverId);
|
||||
console.log("tripId : ", tripId);
|
||||
const bizTripApproval = {
|
||||
id: id,
|
||||
tripId: tripId,
|
||||
approverId: approverId,
|
||||
approveStatus: approveStatus
|
||||
};
|
||||
|
||||
// Ajax 전송
|
||||
$.ajax({
|
||||
url: '/api/bizTrip/approval',
|
||||
method: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(bizTripApproval),
|
||||
success: function(data) {
|
||||
|
||||
|
||||
|
||||
Swal.fire({
|
||||
title: data.msg,
|
||||
text: '목록으로 이동하시겠습니까?',
|
||||
icon: 'success',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: '목록',
|
||||
cancelButtonText: '취소'
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
location.href = "/itn/bizTrip/list";
|
||||
} else if (result.isDismissed) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
error: function(xhr, data) {
|
||||
|
||||
fn_failedAlert("실패", data.msg);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -1,8 +1,21 @@
|
||||
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
||||
<!-- layout:decorate="layout">-->
|
||||
<head th:replace="~{fragments/header :: headerFragment}"/>
|
||||
<body class="hold-transition sidebar-mini layout-fixed" layout:fragment="body">
|
||||
<html xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
|
||||
|
||||
<head>
|
||||
<!-- <title layout:title-pattern="$DECORATOR_TITLE - MySite">기본 타이틀</title>-->
|
||||
<title layout:fragment="title">ITN ADMIN</title>
|
||||
|
||||
|
||||
|
||||
<!-- 공통 헤더 (스타일/스크립트 포함) -->
|
||||
<th:block th:replace="~{fragments/header :: headerFragment}" />
|
||||
|
||||
<!-- 개별 페이지의 head fragment 주입 위치 -->
|
||||
<layout:fragment th:fragment="head" />
|
||||
</head>
|
||||
|
||||
<body class="hold-transition sidebar-mini layout-fixed" layout:fragment="body">
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -74,7 +74,7 @@
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<div class="icheck-primary">
|
||||
<input type="checkbox" id="remember">
|
||||
<input type="checkbox" id="remember" name="remember-me">
|
||||
<label for="remember">
|
||||
Remember Me
|
||||
</label>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user