동적 탭 관리 기능(회원별 탭 데이터 조회,생성 및 삭제)

This commit is contained in:
HyeonJongKim 2024-08-22 13:32:26 +09:00
parent 2c49552a7b
commit c9f30c4da3
14 changed files with 490 additions and 74 deletions

View File

@ -0,0 +1,52 @@
package com.bpgroup.poc.admin.app.tab;
import com.bpgroup.poc.admin.domain.admin.entity.Admin;
import com.bpgroup.poc.admin.domain.admin.service.AdminService;
import com.bpgroup.poc.admin.domain.tab.entity.Tab;
import com.bpgroup.poc.admin.domain.tab.service.TabAddCommand;
import com.bpgroup.poc.admin.domain.tab.service.TabService;
import com.bpgroup.poc.admin.web.main.admin.tab.reqres.TabCreate;
import com.bpgroup.poc.admin.web.main.admin.tab.reqres.TabDelete;
import com.bpgroup.poc.admin.web.main.admin.tab.reqres.TabFind;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@RequiredArgsConstructor
@Transactional
public class TabAppService {
private final TabService tabService;
private final AdminService adminService;
public List<TabFind.Response> findTabList() {
String userName = SecurityContextHolder.getContext().getAuthentication().getName();
Admin findAdmin = adminService.find(userName).orElseThrow(() -> new IllegalArgumentException("Not found admin"));
return tabService.find(findAdmin.getId());
}
public TabCreate.Response addTab(TabCreate.Request request) {
String userName = SecurityContextHolder.getContext().getAuthentication().getName();
Admin findAdmin = adminService.find(userName).orElseThrow(() -> new IllegalArgumentException("Not found admin"));
Tab tab = tabService.create(
TabAddCommand.of(
request.getName(),
request.getUrl(),
findAdmin
)
);
return TabCreate.Response.success(tab);
}
public TabDelete.Response deleteTab(TabDelete.Request request) {
tabService.delete(request.getId());
return TabDelete.Response.success();
}
}

View File

@ -0,0 +1,39 @@
package com.bpgroup.poc.admin.domain.tab.entity;
import com.bpgroup.poc.admin.domain.BaseEntity;
import com.bpgroup.poc.admin.domain.admin.entity.Admin;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@Entity
@Table(name = "tab")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Tab extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", length = 100, nullable = false)
private String name;
@Column(name = "url", length = 100, nullable = false)
private String url;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="admin_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private Admin admin;
private Tab(String name, String url, Admin admin) {
this.name = name;
this.url = url;
this.admin = admin;
}
public static Tab getNewInstanceForCreateOf(String name, String url, Admin admin) {
return new Tab(name, url, admin);
}
}

View File

@ -0,0 +1,10 @@
package com.bpgroup.poc.admin.domain.tab.entity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface TabRepository extends JpaRepository<Tab, Long> {
List<Tab> findByAdminId(long adminId);
}

View File

@ -0,0 +1,31 @@
package com.bpgroup.poc.admin.domain.tab.service;
import com.bpgroup.poc.admin.domain.admin.entity.Admin;
import com.bpgroup.poc.admin.domain.tab.entity.Tab;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class TabAddCommand {
@NotBlank
private final String name;
@NotBlank
private final String url;
@NotNull
private final Admin admin;
public static TabAddCommand of(String name, String url, Admin admin) {
return new TabAddCommand(name, url, admin);
}
public Tab toEntity() {
return Tab.getNewInstanceForCreateOf(name, url, admin);
}
}

View File

@ -0,0 +1,41 @@
package com.bpgroup.poc.admin.domain.tab.service;
import com.bpgroup.poc.admin.domain.tab.entity.Tab;
import com.bpgroup.poc.admin.domain.tab.entity.TabRepository;
import com.bpgroup.poc.admin.web.main.admin.tab.reqres.TabFind;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Validated
@Transactional
public class TabService {
private final TabRepository tabRepository;
public List<TabFind.Response> find(Long adminId) {
List<Tab> findTabList = tabRepository.findByAdminId(adminId);
return findTabList.stream()
.map(TabFind.Response::of)
.collect(Collectors.toList());
}
public Tab create(
@NotNull @Valid TabAddCommand command
) {
return tabRepository.save(command.toEntity());
}
public void delete( @NotNull Long id) {
tabRepository.deleteById(id);
}
}

View File

@ -18,6 +18,8 @@ import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
@ -67,7 +69,7 @@ public class SecurityConfig {
private void configureAuthorization(HttpSecurity http) throws Exception { private void configureAuthorization(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(c -> c http.authorizeHttpRequests(c -> c
.requestMatchers("/css/**", "/images/**", "/js/**", "/font/**", "/favicon.ico").permitAll() .requestMatchers("/css/**", "/images/**", "/js/**", "/font/**", "/favicon.ico","/admin/tab/**").permitAll()
.requestMatchers("/common/modal/**", "/csrf").permitAll() .requestMatchers("/common/modal/**", "/csrf").permitAll()
.requestMatchers(LOGIN_PATH, LOGOUT_PATH, ERROR_PATH).permitAll() .requestMatchers(LOGIN_PATH, LOGOUT_PATH, ERROR_PATH).permitAll()
.anyRequest() .anyRequest()

View File

@ -0,0 +1,44 @@
package com.bpgroup.poc.admin.web.main.admin.tab;
import com.bpgroup.poc.admin.app.tab.TabAppService;
import com.bpgroup.poc.admin.web.main.admin.tab.reqres.TabCreate;
import com.bpgroup.poc.admin.web.main.admin.tab.reqres.TabDelete;
import com.bpgroup.poc.admin.web.main.admin.tab.reqres.TabFind;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequiredArgsConstructor
@RequestMapping("/admin")
public class TabRestController {
private final TabAppService tabAppService;
@GetMapping("/tab/list")
public ResponseEntity<?> findTab() {
List<TabFind.Response> responses = tabAppService.findTabList();
return ResponseEntity.ok(responses);
}
@PostMapping("/tab/add")
public ResponseEntity<?> addTab(
@RequestBody @Valid TabCreate.Request request ,
BindingResult bindingResult
) {
TabCreate.Response response = tabAppService.addTab(request);
return ResponseEntity.ok(response);
}
@PostMapping("/tab/delete")
public ResponseEntity<?> deleteTab(
@RequestBody @Valid TabDelete.Request request
) {
TabDelete.Response response = tabAppService.deleteTab(request);
return ResponseEntity.ok(response);
}
}

View File

@ -0,0 +1,46 @@
package com.bpgroup.poc.admin.web.main.admin.tab.reqres;
import com.bpgroup.poc.admin.domain.tab.entity.Tab;
import com.bpgroup.poc.admin.web.common.reqres.CommonResponse;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.Getter;
import lombok.ToString;
public class TabCreate {
@Data
public static class Request {
@NotBlank
private String name;
@NotBlank
private String url;
private String LoginId;
}
@Getter
@ToString
public static class Response extends CommonResponse {
public Long id;
public static Response success(Tab tab) {
Response response = new Response();
response.resultCode = "0000";
response.resultMessage = "Success";
response.id = tab.getId();
return response;
}
public static Response fail() {
Response response = new Response();
response.resultCode = "9999";
response.resultMessage = "Fail";
return response;
}
}
}

View File

@ -0,0 +1,36 @@
package com.bpgroup.poc.admin.web.main.admin.tab.reqres;
import com.bpgroup.poc.admin.web.common.reqres.CommonResponse;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.Getter;
import lombok.ToString;
public class TabDelete {
@Data
public static class Request {
@NotNull
private Long id;
}
@Getter
@ToString
public static class Response extends CommonResponse {
public static TabDelete.Response success() {
TabDelete.Response response = new TabDelete.Response();
response.resultCode = "0000";
response.resultMessage = "Success";
return response;
}
public static TabDelete.Response fail(String resultMessage) {
TabDelete.Response response = new TabDelete.Response();
response.resultCode = "9999";
response.resultMessage = resultMessage;
return response;
}
}
}

View File

@ -0,0 +1,29 @@
package com.bpgroup.poc.admin.web.main.admin.tab.reqres;
import com.bpgroup.poc.admin.domain.tab.entity.Tab;
import lombok.Getter;
import lombok.ToString;
import java.util.List;
public class TabFind {
@Getter
@ToString
public static class Response {
private Long id;
private String name;
private String url;
private String loginId;
public static Response of(Tab tab) {
Response response = new Response();
response.id = tab.getId();
response.name = tab.getName();
response.url = tab.getUrl();
response.loginId = tab.getAdmin().getLoginId();
return response;
}
}
}

View File

@ -876,7 +876,7 @@ body.login select {
.content { .content {
position: relative; position: relative;
flex: 1; flex: 1;
padding: 70px 30px 50px 30px; padding: 15px 15px 15px 15px;
} }
.con_wrap h3 { .con_wrap h3 {
display: inline-block; display: inline-block;
@ -904,6 +904,15 @@ body.login select {
/* Tab Style */ /* Tab Style */
.close-button {
background-color: transparent;
border: none;
color: red;
cursor: pointer;
font-size: 18px;
font-weight: bold;
}
.tab-list { .tab-list {
list-style-type: none; list-style-type: none;
margin: 0; margin: 0;

View File

@ -34,6 +34,152 @@
</div> </div>
</div> </div>
</main> </main>
<th:block layout:fragment="script"></th:block> <th:block layout:fragment="tabActionScript">
<script th:inline="javascript">
let tabContentCache = {};
Reqhelper.reqGetJson('/admin/tab/list', function(data) {
data.forEach(function(item) {
loadTab(item.id, item.name, item.url);
})
});
function loadTab(id,tabName, url) {
const newTab = $('<li></li>').addClass('tab-item');
const newTabLink = $('<a></a>').addClass('tab-link')
.attr('id', `${id}-tab`)
.attr('href', `#${id}`)
.attr('role', 'tab')
.attr('aria-controls', `${id}`)
.attr('aria-selected', 'false')
.text(tabName)
.click(function(event) {
openTab(event, id);
loadTabContent(id, url);
});
const closeButton = $('<span></span>').addClass('close-button')
.text('X')
.click(function(event) {
$(`#${id}`).remove();
$(`#${id}-tab`).parent().remove();
event.stopPropagation();
Reqhelper.reqPostJson('/admin/tab/delete', {
id : id
}, function() {
console.log('Tab deleted successfully');
}, function() {
console.log('Failed to delete tab');
})
});
newTabLink.append(closeButton);
newTab.append(newTabLink);
$('#myTab').append(newTab);
const newTabContent = $('<div></div>').addClass('tab-pane')
.attr('id', id)
.attr('role', 'tabpanel')
.attr('aria-labelledby', `${id}-tab`)
.on('input', 'input, textarea', function() {
tabContentCache[id] = newTabContent.html();
});
$('#myTabContent').append(newTabContent);
}
function addTab(tabName,url) {
Reqhelper.reqPostJson('/admin/tab/add', {
name: tabName,
url: url
}, function(response) {
console.log('Tab created successfully');
const tabId = response.id;
const newTab = $('<li></li>').addClass('tab-item');
const newTabLink = $('<a></a>').addClass('tab-link')
.attr('id', `${tabId}-tab`)
.attr('href', `#${tabId}`)
.attr('role', 'tab')
.attr('aria-controls', `${tabId}`)
.attr('aria-selected', 'false')
.text(tabName)
.click(function(event) {
openTab(event, tabId);
loadTabContent(tabId, url);
});
const closeButton = $('<span></span>').addClass('close-button')
.text('X')
.click(function(event) {
$(`#${tabId}`).remove();
$(`#${tabId}-tab`).parent().remove();
event.stopPropagation();
Reqhelper.reqPostJson('/admin/tab/delete', {
id : tabId
}, function() {
console.log('Tab deleted successfully');
}, function() {
console.log('Failed to delete tab');
})
});
newTabLink.append(closeButton);
newTab.append(newTabLink);
$('#myTab').append(newTab);
const newTabContent = $('<div></div>').addClass('tab-pane')
.attr('id', tabId)
.attr('role', 'tabpanel')
.attr('aria-labelledby', `${tabId}-tab`)
.on('input', 'input, textarea', function() {
tabContentCache[tabId] = newTabContent.html();
});
$('#myTabContent').append(newTabContent);
}, function() {
console.log('Failed to create tab');
});
}
function loadTabContent(tabId, url) {
if (tabContentCache[tabId]) {
$('#' + tabId).html(tabContentCache[tabId]);
} else {
$.ajax({
url: url,
type: 'GET',
success: function(data) {
tabContentCache[tabId] = data;
$('#' + tabId).html(data);
},
error: function() {
alert('Failed to load tab content');
}
});
}
}
function openTab(evt, tabId) {
$('.tab-pane').css('display', 'none');
$('.tab-link').each(function() {
$(this).removeClass('active');
});
$(`#${tabId}`).css('display', 'block');
$(evt.currentTarget).addClass('active');
}
$(function() {
$("#myTab").sortable({
placeholder: "ui-state-highlight",
update: function(event, ui) {
}
});
$("#myTab").disableSelection();
});
</script>
</th:block>
</body> </body>
</html> </html>

View File

@ -14,7 +14,7 @@
<ul class="sub_menu"> <ul class="sub_menu">
<li th:each="menu : ${menuGroup.getMenuChildren()}"> <li th:each="menu : ${menuGroup.getMenuChildren()}">
<a th:with="path= ${menu.getUri()}" <a th:with="path= ${menu.getUri()}"
th:classappend="${pathInfo.activeClass(path)}" onclick="addTab(this.textContent)"><span th:classappend="${pathInfo.activeClass(path)}" href="#" th:attr="data-path=${path}" onclick="addTab(this.textContent, this.getAttribute('data-path'))"><span
th:text="${menu.getName()}"></span></a> th:text="${menu.getName()}"></span></a>
</li> </li>
</ul> </ul>

View File

@ -16,75 +16,6 @@
</th:block> </th:block>
</th:block> </th:block>
</body> </body>
<th:block layout:fragment="script"> <th:block layout:fragment="script" th:replace="~{layout/common.html :: tabActionScript}">
<script th:inline="javascript">
let tabIdCounter = 1;
function addTab(tabName) {
const tabId = 'tab' + tabIdCounter++;
const newTab = $('<li></li>').addClass('tab-item');
const newTabLink = $('<a></a>').addClass('tab-link')
.attr('id', `${tabId}-tab`)
.attr('href', `#${tabId}`)
.attr('role', 'tab')
.attr('aria-controls', `${tabId}`)
.attr('aria-selected', 'false')
.text(tabName)
.click(function(event) {
openTab(event, tabId);
loadTabContent(tabName, '/admin/management')
});
newTab.append(newTabLink);
$('#myTab').append(newTab);
const newTabContent = $('<div></div>').addClass('tab-pane')
.attr('id', tabId)
.attr('role', 'tabpanel')
.attr('aria-labelledby', `${tabId}-tab`)
$('#myTabContent').append(newTabContent);
}
function openTab(evt, tabId) {
$('.tab-pane').css('display', 'none');
$('.tab-link').each(function() {
$(this).removeClass('active');
});
$(`#${tabId}`).css('display', 'block');
$(evt.currentTarget).addClass('active');
}
$(function() {
$("#myTab").sortable({
placeholder: "ui-state-highlight",
update: function(event, ui) {
}
});
$("#myTab").disableSelection();
});
function loadTabContent(tabName, url) {
$.ajax({
url: url,
type: 'GET',
success: function(response) {
}
});
}
function adjustParentDivWidth() {
var tabListWidth = $('.tab-list').width();
$('.parent-div').css('width', tabListWidth + 'px');
}
$(document).ready(function() {
adjustParentDivWidth();
$('.tab-item').on('click', adjustParentDivWidth);
});
</script>
</th:block> </th:block>
</html> </html>