로그인 로그 및 단어사전 수정자 추가
This commit is contained in:
parent
7388ea931b
commit
de080a06d6
@ -0,0 +1,66 @@
|
||||
package com.itn.admin.cmn.config;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class CustomUserDetails implements UserDetails {
|
||||
|
||||
private final String username;
|
||||
private final String password;
|
||||
private final String userId;
|
||||
private final String id;
|
||||
private final Collection<? extends GrantedAuthority> authorities;
|
||||
|
||||
public CustomUserDetails(String username, String password, String userId, String id, Collection<? extends GrantedAuthority> authorities) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.userId = userId;
|
||||
this.id = id;
|
||||
this.authorities = authorities;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return authorities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,6 @@ package com.itn.admin.cmn.config;
|
||||
import com.itn.admin.itn.user.mapper.UserMapper;
|
||||
import com.itn.admin.itn.user.mapper.domain.UserVO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
@ -31,7 +30,7 @@ public class CustomUserDetailsService implements UserDetailsService {
|
||||
GrantedAuthority authority = new SimpleGrantedAuthority(user.getRole().name());
|
||||
Collection<? extends GrantedAuthority> authorities = Collections.singletonList(authority);
|
||||
|
||||
// User 객체 생성
|
||||
return new User(user.getUserId(), user.getPassword(), authorities);
|
||||
// CustomUserDetails 객체 생성
|
||||
return new CustomUserDetails(user.getUserId(), user.getPassword(), user.getUserId(), user.getId(), authorities);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.itn.admin.cmn.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.itn.admin.itn.user.service.UserService;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@ -26,9 +27,11 @@ import java.io.PrintWriter;
|
||||
public class SecurityConfig {
|
||||
|
||||
private final CustomUserDetailsService customUserDetailsService;
|
||||
private final UserService userService;
|
||||
|
||||
public SecurityConfig(CustomUserDetailsService customUserDetailsService) {
|
||||
public SecurityConfig(CustomUserDetailsService customUserDetailsService, UserService userService) {
|
||||
this.customUserDetailsService = customUserDetailsService;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ -89,8 +92,10 @@ public class SecurityConfig {
|
||||
public AuthenticationSuccessHandler customAuthenticationSuccessHandler() {
|
||||
return (request, response, authentication) -> {
|
||||
// 디버깅 정보를 콘솔에 출력
|
||||
System.out.println("Authentication successful. Username: " + authentication.getName());
|
||||
System.out.println("Authorities: " + authentication.getAuthorities());
|
||||
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
|
||||
String userId = userDetails.getUserId();
|
||||
String id = userDetails.getId();
|
||||
userService.loginLog(id);
|
||||
response.setStatus(HttpStatus.OK.value());
|
||||
response.sendRedirect("/");
|
||||
};
|
||||
|
||||
19
src/main/java/com/itn/admin/cmn/config/SecurityUtil.java
Normal file
19
src/main/java/com/itn/admin/cmn/config/SecurityUtil.java
Normal file
@ -0,0 +1,19 @@
|
||||
package com.itn.admin.cmn.config;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
public class SecurityUtil {
|
||||
|
||||
public static String getCurrentUserId() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication != null && authentication.isAuthenticated()) {
|
||||
Object principal = authentication.getPrincipal();
|
||||
if (principal instanceof CustomUserDetails) {
|
||||
return ((CustomUserDetails) principal).getId();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -3,12 +3,14 @@ package com.itn.admin.cmn.config;
|
||||
import com.itn.admin.itn.user.mapper.domain.UserVO;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
@Slf4j
|
||||
public class UserInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Override
|
||||
@ -33,6 +35,8 @@ public class UserInterceptor implements HandlerInterceptor {
|
||||
userName = principal.toString();
|
||||
}
|
||||
|
||||
log.info("userId : [{}]",userId.toString());
|
||||
log.info("userName : [{}]",userName.toString());
|
||||
modelAndView.addObject("userId", userId);
|
||||
modelAndView.addObject("userName", userName);
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ public class DictionaryVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
private String userId;
|
||||
private String koreanWord;
|
||||
private String englishWord;
|
||||
private String abbreviation;
|
||||
@ -35,6 +36,7 @@ public class DictionaryVO implements Serializable {
|
||||
private String frstRegisterId;
|
||||
private String frstRegistPnttm;
|
||||
private String lastUpdusrId;
|
||||
private String lastUpdusrName;
|
||||
private String lastUpdtPnttm;
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.itn.admin.itn.dictionary.service.impl;
|
||||
|
||||
import com.itn.admin.cmn.config.SecurityUtil;
|
||||
import com.itn.admin.itn.dictionary.mapper.DictionaryMapper;
|
||||
import com.itn.admin.itn.dictionary.mapper.domain.DictionaryVO;
|
||||
import com.itn.admin.itn.dictionary.service.DictionaryService;
|
||||
@ -30,6 +31,15 @@ public class DictionaryServiceImpl implements DictionaryService {
|
||||
|
||||
log.info("Received data for update: [{}]", dictionaryMap);
|
||||
|
||||
// 현재 인증된 사용자 정보 가져오기
|
||||
String userId = SecurityUtil.getCurrentUserId();
|
||||
if (userId == null) {
|
||||
log.warn("Failed to retrieve current user ID.");
|
||||
throw new IllegalStateException("Current user ID is not available");
|
||||
}
|
||||
log.info("Updating by user: [{}]", userId);
|
||||
|
||||
|
||||
Map<String, Object> data = (Map<String, Object>) dictionaryMap.get("data");
|
||||
log.info("data: [{}]", data);
|
||||
String action = (String) dictionaryMap.get("action");
|
||||
@ -46,6 +56,7 @@ public class DictionaryServiceImpl implements DictionaryService {
|
||||
}
|
||||
} else {
|
||||
DictionaryVO dictionaryVO = getSingleDate(dictionaryMap);
|
||||
dictionaryVO.setUserId(userId);
|
||||
if("edit".equals(action)) {
|
||||
log.info("edit :: data for update: [{}]", dictionaryVO);
|
||||
dictionaryMapper.save(dictionaryVO);
|
||||
|
||||
@ -6,6 +6,7 @@ import com.itn.admin.itn.user.mapper.domain.UserVO;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@ -14,7 +15,7 @@ import java.util.stream.Collectors;
|
||||
public interface UserMapper {
|
||||
|
||||
// UserVO save(UserVO userVO);
|
||||
@Select("SELECT user_id AS userId, user_pw AS password, user_name AS username, role FROM users WHERE user_id = #{userId}")
|
||||
@Select("SELECT id, user_id AS userId, user_pw AS password, user_name AS username, role FROM users WHERE user_id = #{userId}")
|
||||
UserVO getUserById(String userId);
|
||||
|
||||
@Select("SELECT id, user_id AS userId, user_pw AS password, user_name AS username, role FROM users WHERE id = #{id}")
|
||||
@ -37,4 +38,7 @@ public interface UserMapper {
|
||||
List<UserVO> findAll(UserVO userVO);
|
||||
|
||||
void updateRole(UserVO user);
|
||||
|
||||
@Insert("INSERT INTO login_logs (id, FRST_REGIST_PNTTM) VALUES (#{id}, now())")
|
||||
void loginLog(String id);
|
||||
}
|
||||
|
||||
@ -14,4 +14,6 @@ public interface UserService {
|
||||
Map<String, Object> getList(UserVO userVO);
|
||||
|
||||
RestResponse updateRole(String id, Role role);
|
||||
|
||||
void loginLog(String id);
|
||||
}
|
||||
|
||||
@ -72,5 +72,11 @@ public class UserServiceImpl implements UserService {
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loginLog(String id) {
|
||||
userMapper.loginLog(id);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -8,17 +8,20 @@
|
||||
<select id="findAll" resultType="dictionaryVO">
|
||||
<!-- <select id="findAll" resultType="com.itn.admin.itn.dictionary.mapper.domain.DictionaryVO" parameterType="com.itn.admin.itn.dictionary.mapper.domain.DictionaryVO">-->
|
||||
SELECT
|
||||
id
|
||||
, KOREAN_WORD
|
||||
, ENGLISH_WORD
|
||||
, ABBREVIATION
|
||||
, IS_CONFIRMED
|
||||
, IS_ACTIVE
|
||||
, FRST_REGISTER_ID
|
||||
, FRST_REGIST_PNTTM
|
||||
, LAST_UPDUSR_ID
|
||||
, LAST_UPDT_PNTTM
|
||||
FROM dictionary
|
||||
a.id
|
||||
, a.KOREAN_WORD
|
||||
, a.ENGLISH_WORD
|
||||
, a.ABBREVIATION
|
||||
, a.IS_CONFIRMED
|
||||
, a.IS_ACTIVE
|
||||
, a.FRST_REGISTER_ID
|
||||
, a.FRST_REGIST_PNTTM
|
||||
, a.LAST_UPDUSR_ID
|
||||
, a.LAST_UPDT_PNTTM
|
||||
, b.USER_NAME as lastUpdusrName
|
||||
FROM dictionary a
|
||||
LEFT JOIN users b
|
||||
ON a.LAST_UPDUSR_ID = b.ID
|
||||
|
||||
</select>
|
||||
|
||||
@ -41,6 +44,9 @@
|
||||
<if test="isActive != null">
|
||||
IS_ACTIVE = #{isActive},
|
||||
</if>
|
||||
<if test="userId != null">
|
||||
LAST_UPDUSR_ID = #{userId},
|
||||
</if>
|
||||
LAST_UPDT_PNTTM = NOW()
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
@ -28,382 +28,388 @@
|
||||
|
||||
<body layout:fragment="body">
|
||||
|
||||
<div class="wrapper">
|
||||
<div th:replace="~{fragments/top_nav :: topFragment}"/>
|
||||
<div class="wrapper">
|
||||
<div th:replace="~{fragments/top_nav :: topFragment}"/>
|
||||
|
||||
<!-- Main Sidebar Container -->
|
||||
<aside class="main-sidebar sidebar-dark-primary elevation-4"
|
||||
th:insert="~{fragments/mainsidebar :: sidebarFragment}">
|
||||
</aside>
|
||||
|
||||
<!-- Content Wrapper. Contains page content -->
|
||||
<div class="content-wrapper">
|
||||
<!-- Content Header (Page header) -->
|
||||
<div class="content-header">
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">
|
||||
<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>
|
||||
</ol>
|
||||
</div><!-- /.col -->
|
||||
</div><!-- /.row -->
|
||||
</div><!-- /.container-fluid -->
|
||||
</div>
|
||||
<!-- /.content-header -->
|
||||
<!-- Main content -->
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<!-- /.card -->
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">목록 - 클릭하면 수정 가능합니다.</h3>
|
||||
</div>
|
||||
<!-- /.card-header -->
|
||||
<div class="card-body">
|
||||
<table id="dicTb" class="display">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%;">ID</th>
|
||||
<th style="width: 5%;">삭제</th>
|
||||
<th>한글단어</th>
|
||||
<th>영어단어</th>
|
||||
<th>약어</th>
|
||||
<th>확정</th>
|
||||
<!-- <th>isActive</th>-->
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<!-- /.card-body -->
|
||||
</div>
|
||||
<!-- /.card -->
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
</div>
|
||||
<!-- /.container-fluid -->
|
||||
</section>
|
||||
<!-- /Main content -->
|
||||
<!-- Main Sidebar Container -->
|
||||
<aside class="main-sidebar sidebar-dark-primary elevation-4"
|
||||
th:insert="~{fragments/mainsidebar :: sidebarFragment}">
|
||||
</aside>
|
||||
|
||||
<!-- Content Wrapper. Contains page content -->
|
||||
<div class="content-wrapper">
|
||||
<!-- Content Header (Page header) -->
|
||||
<div class="content-header">
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">
|
||||
<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>
|
||||
</ol>
|
||||
</div><!-- /.col -->
|
||||
</div><!-- /.row -->
|
||||
</div><!-- /.container-fluid -->
|
||||
</div>
|
||||
<!-- /.content-header -->
|
||||
<!-- Main content -->
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<!-- /.card -->
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">목록 - 클릭하면 수정 가능합니다.</h3>
|
||||
</div>
|
||||
<!-- /.card-header -->
|
||||
<div class="card-body">
|
||||
<table id="dicTb" class="display">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%;">ID</th>
|
||||
<th style="width: 5%;">삭제</th>
|
||||
<th>한글단어</th>
|
||||
<th>영어단어</th>
|
||||
<th>약어</th>
|
||||
<th>확정</th>
|
||||
<th>최근수정자</th>
|
||||
<th>수정시간</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
<!-- /.card-body -->
|
||||
</div>
|
||||
<!-- /.card -->
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
</div>
|
||||
<!-- /.container-fluid -->
|
||||
</section>
|
||||
<!-- /Main content -->
|
||||
|
||||
|
||||
<!-- /.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 -->
|
||||
|
||||
|
||||
<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" type="text/css" href="https://cdn.datatables.net/2.0.7/css/dataTables.dataTables.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/buttons/3.0.2/css/buttons.dataTables.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/select/2.0.2/css/select.dataTables.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/datetime/1.5.2/css/dataTables.dateTime.min.css">
|
||||
<link rel="stylesheet" th:href="@{/plugins/datatable_editor/css/editor.dataTables.css}">
|
||||
|
||||
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.js"></script>
|
||||
<script src="https://cdn.datatables.net/buttons/3.0.2/js/dataTables.buttons.js"></script>
|
||||
<script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.dataTables.js"></script>
|
||||
<script src="https://cdn.datatables.net/select/2.0.2/js/dataTables.select.js"></script>
|
||||
<script src="https://cdn.datatables.net/select/2.0.2/js/select.dataTables.js"></script>
|
||||
<script src="https://cdn.datatables.net/datetime/1.5.2/js/dataTables.dateTime.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/4.6.3/papaparse.min.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.js"></script>
|
||||
<script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.html5.min.js"></script>
|
||||
<script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.print.min.js"></script>
|
||||
<!-- /.content-wrapper -->
|
||||
<footer class="main-footer"
|
||||
th:insert="~{fragments/footer :: footerFragment}">
|
||||
</footer>
|
||||
|
||||
<script th:src="@{/plugins/datatable_editor/js/dataTables.editor.js}"></script>
|
||||
<script th:src="@{/plugins/datatable_editor/js/editor.dataTables.js}"></script>
|
||||
<script>
|
||||
// https://editor.datatables.net/examples/simple/simple.html
|
||||
var editor = new DataTable.Editor({
|
||||
ajax: {
|
||||
type: 'POST',
|
||||
url: '/dictionary/api/update',
|
||||
contentType: 'application/json', // Content-Type을 application/json으로 설정
|
||||
data: function (d) {
|
||||
return JSON.stringify(d); // 데이터를 JSON 문자열로 변환
|
||||
}
|
||||
},
|
||||
idSrc: 'id', // 이렇게 `idSrc` 속성을 사용하여 식별자를 지정합니다.
|
||||
table: '#dicTb',
|
||||
formOptions: {
|
||||
inline: {
|
||||
onBlur: 'submit'
|
||||
}
|
||||
},
|
||||
fields: [
|
||||
// {
|
||||
// label: 'ID:', // "ID" 대신 다른 레이블을 사용하고 싶다면 여기를 변경
|
||||
// name: 'id'
|
||||
// },
|
||||
{
|
||||
label: '한국어 단어:', // "koreanWord"의 레이블을 "한국어 단어"로 변경
|
||||
name: 'koreanWord', attr: { required: 'required' }
|
||||
},
|
||||
{
|
||||
label: '영어 단어:', // "englishWord"의 레이블을 "영어 단어"로 변경
|
||||
name: 'englishWord', attr: { required: 'required' }
|
||||
},
|
||||
{
|
||||
label: '약어:', // "abbreviation"의 레이블을 "약어"로 변경
|
||||
name: 'abbreviation', attr: { required: 'required' }
|
||||
},
|
||||
{
|
||||
label: '확정 여부:', // "Is Confirmed"의 레이블을 "확인 여부"로 변경
|
||||
name: 'isConfirmed',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'Yes', value: 'Y' },
|
||||
{ label: 'No', value: 'N' }
|
||||
]
|
||||
}
|
||||
// ,{
|
||||
// label: 'isActive:',
|
||||
// name: 'isActive'
|
||||
// }
|
||||
<!-- Control Sidebar -->
|
||||
<aside class="control-sidebar control-sidebar-dark">
|
||||
<!-- Control sidebar content goes here -->
|
||||
</aside>
|
||||
<!-- /.control-sidebar -->
|
||||
</div>
|
||||
<!-- ./wrapper -->
|
||||
|
||||
|
||||
<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" type="text/css" href="https://cdn.datatables.net/2.0.7/css/dataTables.dataTables.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/buttons/3.0.2/css/buttons.dataTables.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/select/2.0.2/css/select.dataTables.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/datetime/1.5.2/css/dataTables.dateTime.min.css">
|
||||
<link rel="stylesheet" th:href="@{/plugins/datatable_editor/css/editor.dataTables.css}">
|
||||
|
||||
<script src="https://cdn.datatables.net/2.0.7/js/dataTables.js"></script>
|
||||
<script src="https://cdn.datatables.net/buttons/3.0.2/js/dataTables.buttons.js"></script>
|
||||
<script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.dataTables.js"></script>
|
||||
<script src="https://cdn.datatables.net/select/2.0.2/js/dataTables.select.js"></script>
|
||||
<script src="https://cdn.datatables.net/select/2.0.2/js/select.dataTables.js"></script>
|
||||
<script src="https://cdn.datatables.net/datetime/1.5.2/js/dataTables.dateTime.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/4.6.3/papaparse.min.js"></script>
|
||||
|
||||
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.js"></script>
|
||||
<script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.html5.min.js"></script>
|
||||
<script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.print.min.js"></script>
|
||||
|
||||
<script th:src="@{/plugins/datatable_editor/js/dataTables.editor.js}"></script>
|
||||
<script th:src="@{/plugins/datatable_editor/js/editor.dataTables.js}"></script>
|
||||
<script>
|
||||
// https://editor.datatables.net/examples/simple/simple.html
|
||||
var editor = new DataTable.Editor({
|
||||
ajax: {
|
||||
type: 'POST',
|
||||
url: '/dictionary/api/update',
|
||||
contentType: 'application/json', // Content-Type을 application/json으로 설정
|
||||
data: function (d) {
|
||||
return JSON.stringify(d); // 데이터를 JSON 문자열로 변환
|
||||
}
|
||||
},
|
||||
idSrc: 'id', // 이렇게 `idSrc` 속성을 사용하여 식별자를 지정합니다.
|
||||
table: '#dicTb',
|
||||
formOptions: {
|
||||
inline: {
|
||||
onBlur: 'submit'
|
||||
}
|
||||
},
|
||||
fields: [
|
||||
// {
|
||||
// label: 'ID:', // "ID" 대신 다른 레이블을 사용하고 싶다면 여기를 변경
|
||||
// name: 'id'
|
||||
// },
|
||||
{
|
||||
label: '한국어 단어:', // "koreanWord"의 레이블을 "한국어 단어"로 변경
|
||||
name: 'koreanWord', attr: { required: 'required' }
|
||||
},
|
||||
{
|
||||
label: '영어 단어:', // "englishWord"의 레이블을 "영어 단어"로 변경
|
||||
name: 'englishWord', attr: { required: 'required' }
|
||||
},
|
||||
{
|
||||
label: '약어:', // "abbreviation"의 레이블을 "약어"로 변경
|
||||
name: 'abbreviation', attr: { required: 'required' }
|
||||
},
|
||||
{
|
||||
label: '확정 여부:', // "Is Confirmed"의 레이블을 "확인 여부"로 변경
|
||||
name: 'isConfirmed',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'Yes', value: 'Y' },
|
||||
{ label: 'No', value: 'N' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '최근수정자:',
|
||||
name: 'lastUpdusrName'
|
||||
},
|
||||
{
|
||||
label: '수정시간:',
|
||||
name: 'lastUpdtPnttm',
|
||||
type: 'datetime',
|
||||
format: 'YYYY-MM-DD HH:mm:ss'
|
||||
}
|
||||
]
|
||||
|
||||
});
|
||||
|
||||
// Upload Editor - triggered from the import button. Used only for uploading a file to the browser
|
||||
var uploadEditor = new DataTable.Editor({
|
||||
ajax: function (method, url, data, success, error) {
|
||||
// CSV import일 때만 action을 수정
|
||||
if (data.action === 'create') {
|
||||
data.action = 'csvUpdate';
|
||||
}
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: method,
|
||||
data: JSON.stringify(data),
|
||||
contentType: 'application/json',
|
||||
success: success,
|
||||
error: error
|
||||
});
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
label: 'CSV file:',
|
||||
name: 'csv',
|
||||
type: 'upload',
|
||||
ajax: function (files, done) {
|
||||
// Ajax override of the upload so we can handle the file locally. Here we use Papa
|
||||
// to parse the CSV.
|
||||
Papa.parse(files[0], {
|
||||
header: true,
|
||||
skipEmptyLines: true,
|
||||
complete: function (results) {
|
||||
if (results.errors.length) {
|
||||
console.log(results);
|
||||
uploadEditor
|
||||
.field('csv')
|
||||
.error('CSV parsing error: ' + results.errors[0].message);
|
||||
}
|
||||
else {
|
||||
selectColumns(editor, results.data, results.meta.fields);
|
||||
}
|
||||
|
||||
// Tell Editor the upload is complete - the array is a list of file
|
||||
// id's, which the value of doesn't matter in this case.
|
||||
done([0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
function selectColumns(editor, csv, header) {
|
||||
let selectEditor = new DataTable.Editor();
|
||||
let fields = editor.order();
|
||||
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
let field = editor.field(fields[i]);
|
||||
|
||||
selectEditor.add({
|
||||
label: field.label(),
|
||||
name: field.name(),
|
||||
type: 'select',
|
||||
options: header,
|
||||
def: header[i]
|
||||
});
|
||||
}
|
||||
|
||||
selectEditor.create({
|
||||
title: 'Map CSV fields',
|
||||
buttons: 'Import ' + csv.length + ' records',
|
||||
message:
|
||||
'각 필드에 데이터를 사용할 CSV 열을 선택합니다.',
|
||||
onComplete: 'none'
|
||||
});
|
||||
|
||||
selectEditor.on('submitComplete', function (e, json, data, action) {
|
||||
// Use the host Editor instance to show a multi-row create form allowing the user to submit the data.
|
||||
editor.create(csv.length, {
|
||||
title: 'Confirm import',
|
||||
buttons: 'Submit',
|
||||
message:
|
||||
'<i>Submit</i> 버튼을 클릭하여 ' +
|
||||
csv.length +
|
||||
'개의 데이터 가져오기를 확인합니다. 원하는 경우 아래 필드를 클릭하여 필드 값을 재정의하여 공통 값을 설정할 수 있습니다.'
|
||||
});
|
||||
|
||||
// Upload Editor - triggered from the import button. Used only for uploading a file to the browser
|
||||
var uploadEditor = new DataTable.Editor({
|
||||
ajax: function (method, url, data, success, error) {
|
||||
// CSV import일 때만 action을 수정
|
||||
if (data.action === 'create') {
|
||||
data.action = 'csvUpdate';
|
||||
}
|
||||
$.ajax({
|
||||
url: url,
|
||||
method: method,
|
||||
data: JSON.stringify(data),
|
||||
contentType: 'application/json',
|
||||
success: success,
|
||||
error: error
|
||||
});
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
label: 'CSV file:',
|
||||
name: 'csv',
|
||||
type: 'upload',
|
||||
ajax: function (files, done) {
|
||||
// Ajax override of the upload so we can handle the file locally. Here we use Papa
|
||||
// to parse the CSV.
|
||||
Papa.parse(files[0], {
|
||||
header: true,
|
||||
skipEmptyLines: true,
|
||||
complete: function (results) {
|
||||
if (results.errors.length) {
|
||||
console.log(results);
|
||||
uploadEditor
|
||||
.field('csv')
|
||||
.error('CSV parsing error: ' + results.errors[0].message);
|
||||
}
|
||||
else {
|
||||
selectColumns(editor, results.data, results.meta.fields);
|
||||
}
|
||||
for (var i = 0; i < fields.length; i++) {
|
||||
let field = editor.field(fields[i]);
|
||||
let mapped = DataTable.util.get(field.name())(data);
|
||||
|
||||
// Tell Editor the upload is complete - the array is a list of file
|
||||
// id's, which the value of doesn't matter in this case.
|
||||
done([0]);
|
||||
}
|
||||
for (let j = 0; j < csv.length; j++) {
|
||||
field.multiSet(j, csv[j][mapped]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var table = $('#dicTb').DataTable({
|
||||
"pageLength": 100,
|
||||
ajax: {
|
||||
url: '/dictionary/list/load',
|
||||
type: 'GET',
|
||||
dataSrc: ''
|
||||
},
|
||||
columns: [
|
||||
{ data: 'id' },
|
||||
{
|
||||
data: null,
|
||||
className: 'dt-center editor-delete',
|
||||
defaultContent: '<button><i class="fa fa-trash"/></button>',
|
||||
orderable: false,
|
||||
},
|
||||
{ data: 'koreanWord' },
|
||||
{ data: 'englishWord' },
|
||||
{ data: 'abbreviation' },
|
||||
{
|
||||
data: 'isConfirmed',
|
||||
render: function(data, type, row) {
|
||||
return data === 'Y' ? 'Yes' : 'No';
|
||||
}
|
||||
},
|
||||
{ data: 'lastUpdusrName' },
|
||||
{ data: 'lastUpdtPnttm' }
|
||||
],
|
||||
layout: {
|
||||
topStart: {
|
||||
buttons: [
|
||||
{ extend: 'create', editor: editor },
|
||||
{
|
||||
extend: 'csv',
|
||||
text: 'Export CSV',
|
||||
className: 'btn-space',
|
||||
exportOptions: {
|
||||
orthogonal: null
|
||||
},
|
||||
customize: function (csv) {
|
||||
// UTF-8 BOM 추가
|
||||
return '\uFEFF' + csv;
|
||||
}
|
||||
},
|
||||
{
|
||||
text: 'Import CSV',
|
||||
action: function () {
|
||||
uploadEditor.create({
|
||||
title: 'Export CSV 한 파일에 A B 열이 삭제된 양식으로 작성해 주세요'
|
||||
});
|
||||
}
|
||||
}
|
||||
// { extend: 'edit', editor: editor },
|
||||
// { extend: 'remove', editor: editor }
|
||||
]
|
||||
});
|
||||
function selectColumns(editor, csv, header) {
|
||||
let selectEditor = new DataTable.Editor();
|
||||
let fields = editor.order();
|
||||
}
|
||||
},
|
||||
order: [0, 'asc'],
|
||||
select: {
|
||||
style: 'os',
|
||||
selector: 'td:first-child'
|
||||
}
|
||||
});
|
||||
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
let field = editor.field(fields[i]);
|
||||
// 필드 클릭해서 수정할수 있는 옵션
|
||||
$('#dicTb').on('click', 'tbody td:not(:first-child)', function (e) {
|
||||
editor.inline(this);
|
||||
});
|
||||
editor.on('submitSuccess', function() {
|
||||
// editor.DataTable().destroy();
|
||||
table.ajax.reload(null, false); // 수정이 성공한 후 테이블 데이터 리로드
|
||||
});
|
||||
|
||||
selectEditor.add({
|
||||
label: field.label(),
|
||||
name: field.name(),
|
||||
type: 'select',
|
||||
options: header,
|
||||
def: header[i]
|
||||
});
|
||||
// 삭제 버튼 클릭 이벤트
|
||||
table.on('click', 'td.editor-delete button', function (e) {
|
||||
var tr = $(this).closest('tr');
|
||||
var row = table.row(tr).data();
|
||||
|
||||
var message = '"' + row.koreanWord + '" (' + row.englishWord + ') 삭제 하시겠습니까?';
|
||||
editor.message(message);
|
||||
|
||||
editor.remove(tr, {
|
||||
title: 'Delete record',
|
||||
message: message, // 직접 메시지 전달
|
||||
buttons: 'Delete'
|
||||
});
|
||||
});
|
||||
|
||||
// 데이터 제출 전에 유효성 검사 수행
|
||||
editor.on('preSubmit', function (e, data, action) {
|
||||
|
||||
if(action == 'create'){
|
||||
|
||||
var error = false;
|
||||
var errorMsg = '';
|
||||
|
||||
// Loop through all the data items
|
||||
for (var key in data.data) {
|
||||
var entry = data.data[key];
|
||||
|
||||
// Validate Korean Word
|
||||
if (!entry.koreanWord || entry.koreanWord.trim() === '') {
|
||||
error = true;
|
||||
errorMsg += '한국어 단어를 입력해주세요.\n';
|
||||
}else if (!entry.englishWord || entry.englishWord.trim() === '') {
|
||||
error = true;
|
||||
errorMsg += '영어 단어를 입력해주세요.\n';
|
||||
}else if (!entry.abbreviation || entry.abbreviation.trim() === '') {
|
||||
error = true;
|
||||
errorMsg += '약어를 입력해주세요.\n';
|
||||
}
|
||||
|
||||
selectEditor.create({
|
||||
title: 'Map CSV fields',
|
||||
buttons: 'Import ' + csv.length + ' records',
|
||||
message:
|
||||
'각 필드에 데이터를 사용할 CSV 열을 선택합니다.',
|
||||
onComplete: 'none'
|
||||
});
|
||||
|
||||
selectEditor.on('submitComplete', function (e, json, data, action) {
|
||||
// Use the host Editor instance to show a multi-row create form allowing the user to submit the data.
|
||||
editor.create(csv.length, {
|
||||
title: 'Confirm import',
|
||||
buttons: 'Submit',
|
||||
message:
|
||||
'<i>Submit</i> 버튼을 클릭하여 ' +
|
||||
csv.length +
|
||||
'개의 데이터 가져오기를 확인합니다. 원하는 경우 아래 필드를 클릭하여 필드 값을 재정의하여 공통 값을 설정할 수 있습니다.'
|
||||
});
|
||||
|
||||
for (var i = 0; i < fields.length; i++) {
|
||||
let field = editor.field(fields[i]);
|
||||
let mapped = DataTable.util.get(field.name())(data);
|
||||
|
||||
for (let j = 0; j < csv.length; j++) {
|
||||
field.multiSet(j, csv[j][mapped]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var table = $('#dicTb').DataTable({
|
||||
"pageLength": 100,
|
||||
ajax: {
|
||||
url: '/dictionary/list/load',
|
||||
type: 'GET',
|
||||
dataSrc: ''
|
||||
},
|
||||
columns: [
|
||||
{ data: 'id' },
|
||||
{
|
||||
data: null,
|
||||
className: 'dt-center editor-delete',
|
||||
defaultContent: '<button><i class="fa fa-trash"/></button>',
|
||||
orderable: false,
|
||||
},
|
||||
{ data: 'koreanWord' },
|
||||
{ data: 'englishWord' },
|
||||
{ data: 'abbreviation' },
|
||||
{
|
||||
data: 'isConfirmed',
|
||||
render: function(data, type, row) {
|
||||
return data === 'Y' ? 'Yes' : 'No';
|
||||
}
|
||||
}
|
||||
// ,{ data: 'isActive' }
|
||||
],
|
||||
layout: {
|
||||
topStart: {
|
||||
buttons: [
|
||||
{ extend: 'create', editor: editor },
|
||||
{
|
||||
extend: 'csv',
|
||||
text: 'Export CSV',
|
||||
className: 'btn-space',
|
||||
exportOptions: {
|
||||
orthogonal: null
|
||||
},
|
||||
customize: function (csv) {
|
||||
// UTF-8 BOM 추가
|
||||
return '\uFEFF' + csv;
|
||||
}
|
||||
},
|
||||
{
|
||||
text: 'Import CSV',
|
||||
action: function () {
|
||||
uploadEditor.create({
|
||||
title: 'Export CSV 한 파일에 A B 열이 삭제된 양식으로 작성해 주세요'
|
||||
});
|
||||
}
|
||||
}
|
||||
// { extend: 'edit', editor: editor },
|
||||
// { extend: 'remove', editor: editor }
|
||||
]
|
||||
}
|
||||
},
|
||||
order: [0, 'asc'],
|
||||
select: {
|
||||
style: 'os',
|
||||
selector: 'td:first-child'
|
||||
}
|
||||
});
|
||||
|
||||
// 필드 클릭해서 수정할수 있는 옵션
|
||||
$('#dicTb').on('click', 'tbody td:not(:first-child)', function (e) {
|
||||
editor.inline(this);
|
||||
});
|
||||
editor.on('submitSuccess', function() {
|
||||
// editor.DataTable().destroy();
|
||||
table.ajax.reload(null, false); // 수정이 성공한 후 테이블 데이터 리로드
|
||||
});
|
||||
|
||||
// 삭제 버튼 클릭 이벤트
|
||||
table.on('click', 'td.editor-delete button', function (e) {
|
||||
var tr = $(this).closest('tr');
|
||||
var row = table.row(tr).data();
|
||||
|
||||
var message = '"' + row.koreanWord + '" (' + row.englishWord + ') 삭제 하시겠습니까?';
|
||||
editor.message(message);
|
||||
|
||||
editor.remove(tr, {
|
||||
title: 'Delete record',
|
||||
message: message, // 직접 메시지 전달
|
||||
buttons: 'Delete'
|
||||
});
|
||||
});
|
||||
|
||||
// 데이터 제출 전에 유효성 검사 수행
|
||||
editor.on('preSubmit', function (e, data, action) {
|
||||
|
||||
if(action == 'create'){
|
||||
|
||||
var error = false;
|
||||
var errorMsg = '';
|
||||
|
||||
// Loop through all the data items
|
||||
for (var key in data.data) {
|
||||
var entry = data.data[key];
|
||||
|
||||
// Validate Korean Word
|
||||
if (!entry.koreanWord || entry.koreanWord.trim() === '') {
|
||||
error = true;
|
||||
errorMsg += '한국어 단어를 입력해주세요.\n';
|
||||
}else if (!entry.englishWord || entry.englishWord.trim() === '') {
|
||||
error = true;
|
||||
errorMsg += '영어 단어를 입력해주세요.\n';
|
||||
}else if (!entry.abbreviation || entry.abbreviation.trim() === '') {
|
||||
error = true;
|
||||
errorMsg += '약어를 입력해주세요.\n';
|
||||
}
|
||||
}
|
||||
|
||||
// If any error was detected, prevent submission and display the error message
|
||||
if (error) {
|
||||
this.error(errorMsg);
|
||||
return false; // prevent submission
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
// If any error was detected, prevent submission and display the error message
|
||||
if (error) {
|
||||
this.error(errorMsg);
|
||||
return false; // prevent submission
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
|
||||
</html>
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
<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">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user