대시보드 구현 완료 세세한 오류 수정 진행중
Build & Deploy to K8s / build-and-deploy (push) Successful in 5m4s

This commit is contained in:
2026-04-19 21:15:25 +09:00
parent 937505ab28
commit b3ad787179
70 changed files with 4366 additions and 1368 deletions
@@ -15,7 +15,7 @@ public class DashboardController {
private final DashboardService dashboardService;
// ═══ 대시보드 CRUD ═══
// ═══ 대시보드 CRUD (MENU_INFO 단일 본체) ═══
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> getDashboardList(
@@ -33,11 +33,11 @@ public class DashboardController {
return ResponseEntity.ok(ApiResponse.success(dashboardService.getDashboardList(params)));
}
@GetMapping("/{dashboardId}")
@GetMapping("/{objid}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getDashboardInfo(
@PathVariable String dashboardId) {
@PathVariable String objid) {
Map<String, Object> params = new HashMap<>();
params.put("dashboard_id", dashboardId);
params.put("objid", objid);
Map<String, Object> result = dashboardService.getDashboardInfo(params);
if (result == null) {
return ResponseEntity.ok(ApiResponse.error("대시보드를 찾을 수 없습니다"));
@@ -55,23 +55,23 @@ public class DashboardController {
return ResponseEntity.ok(ApiResponse.success(dashboardService.insertDashboard(body)));
}
@PutMapping("/{dashboardId}")
@PutMapping("/{objid}")
public ResponseEntity<ApiResponse<Void>> updateDashboard(
@PathVariable String dashboardId,
@PathVariable String objid,
@RequestAttribute("user_id") String userId,
@RequestBody Map<String, Object> body) {
body.put("dashboard_id", dashboardId);
body.put("objid", objid);
body.put("user_id", userId);
dashboardService.updateDashboard(body);
return ResponseEntity.ok(ApiResponse.success(null, "수정 완료"));
}
@DeleteMapping("/{dashboardId}")
@DeleteMapping("/{objid}")
public ResponseEntity<ApiResponse<Void>> deleteDashboard(
@PathVariable String dashboardId,
@PathVariable String objid,
@RequestAttribute("user_id") String userId) {
Map<String, Object> params = new HashMap<>();
params.put("dashboard_id", dashboardId);
params.put("objid", objid);
params.put("user_id", userId);
dashboardService.deleteDashboard(params);
return ResponseEntity.ok(ApiResponse.success(null, "삭제 완료"));
@@ -79,36 +79,36 @@ public class DashboardController {
// ═══ 카드 CRUD ═══
@GetMapping("/{dashboardId}/cards")
@GetMapping("/{objid}/cards")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getDashboardCards(
@PathVariable String dashboardId) {
@PathVariable String objid) {
Map<String, Object> params = new HashMap<>();
params.put("dashboard_id", dashboardId);
params.put("menu_objid", objid);
return ResponseEntity.ok(ApiResponse.success(dashboardService.getDashboardCardList(params)));
}
@PostMapping("/{dashboardId}/cards")
@PostMapping("/{objid}/cards")
public ResponseEntity<ApiResponse<Map<String, Object>>> insertDashboardCard(
@PathVariable String dashboardId,
@PathVariable String objid,
@RequestBody Map<String, Object> body) {
body.put("dashboard_id", dashboardId);
body.put("menu_objid", objid);
return ResponseEntity.ok(ApiResponse.success(dashboardService.insertDashboardCard(body)));
}
@PutMapping("/{dashboardId}/cards/{cardId}")
@PutMapping("/{objid}/cards/{cardId}")
public ResponseEntity<ApiResponse<Void>> updateDashboardCard(
@PathVariable String dashboardId,
@PathVariable String objid,
@PathVariable String cardId,
@RequestBody Map<String, Object> body) {
body.put("dashboard_id", dashboardId);
body.put("menu_objid", objid);
body.put("card_id", cardId);
dashboardService.updateDashboardCard(body);
return ResponseEntity.ok(ApiResponse.success(null));
}
@DeleteMapping("/{dashboardId}/cards/{cardId}")
@DeleteMapping("/{objid}/cards/{cardId}")
public ResponseEntity<ApiResponse<Void>> deleteDashboardCard(
@PathVariable String dashboardId,
@PathVariable String objid,
@PathVariable String cardId) {
Map<String, Object> params = new HashMap<>();
params.put("card_id", cardId);
@@ -116,23 +116,11 @@ public class DashboardController {
return ResponseEntity.ok(ApiResponse.success(null));
}
@PutMapping("/{dashboardId}/cards/batch")
@PutMapping("/{objid}/cards/batch")
public ResponseEntity<ApiResponse<Void>> updateCardPositions(
@PathVariable String dashboardId,
@PathVariable String objid,
@RequestBody Map<String, Object> body) {
dashboardService.updateCardPositions(body);
return ResponseEntity.ok(ApiResponse.success(null, "일괄 업데이트 완료"));
}
// ═══ 사이드바 메뉴 ═══
@GetMapping("/sidebar/menu")
public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getSidebarMenu(
@RequestAttribute("company_code") String companyCode,
@RequestAttribute("user_id") String userId) {
Map<String, Object> params = new HashMap<>();
params.put("company_code", companyCode);
params.put("user_id", userId);
return ResponseEntity.ok(ApiResponse.success(dashboardService.getSidebarMenu(params)));
}
}
@@ -17,7 +17,7 @@ public class DashboardService extends BaseService {
private static final String NS = "dashboard.";
// ═══ 대시보드 CRUD ═══
// ═══ 대시보드 CRUD (MENU_INFO 단일 본체) ═══
public Map<String, Object> getDashboardList(Map<String, Object> params) {
commonService.applyPagination(params);
@@ -32,17 +32,32 @@ public class DashboardService extends BaseService {
@Transactional
public Map<String, Object> insertDashboard(Map<String, Object> params) {
String dashboardId = "dash_" + UUID.randomUUID().toString().replace("-", "").substring(0, 12);
params.put("dashboard_id", dashboardId);
if (params.get("icon") == null) {
params.put("icon", "\uD83D\uDCCB");
}
if (params.get("display_order") == null) {
params.put("display_order", 0);
String objid = String.valueOf(System.currentTimeMillis());
params.put("objid", objid);
params.put("writer", params.get("user_id"));
// 개인 대시보드 여부: is_personal=true 이면 USER_ID 채움, 아니면 NULL (회사 공용)
Object isPersonal = params.get("is_personal");
boolean personal = isPersonal != null
&& (Boolean.TRUE.equals(isPersonal) || "true".equalsIgnoreCase(String.valueOf(isPersonal)));
if (!personal) {
params.put("user_id", null);
}
// 회사별 자동 시퀀스 = URL. COMPANY_CODE 내 /숫자 형 URL 중 최대값 + 1
Integer nextSeq = sqlSession.selectOne(NS + "getNextMenuSeq", params);
if (nextSeq == null) nextSeq = 1;
String menuUrl = "/" + nextSeq;
params.put("seq", String.valueOf(nextSeq));
params.put("menu_url", menuUrl);
sqlSession.insert(NS + "insertDashboard", params);
Map<String, Object> result = new HashMap<>();
result.put("dashboard_id", dashboardId);
result.put("objid", objid);
result.put("dashboard_id", objid); // 프론트 하위호환
result.put("menu_url", menuUrl);
result.put("seq", nextSeq);
return result;
}
@@ -97,10 +112,4 @@ public class DashboardService extends BaseService {
}
}
}
// ═══ 사이드바 메뉴 ═══
public List<Map<String, Object>> getSidebarMenu(Map<String, Object> params) {
return sqlSession.selectList(NS + "getSidebarMenu", params);
}
}
@@ -333,6 +333,7 @@
, LANG_KEY
, LANG_KEY_DESC
, MENU_ICON
, USER_ID
) VALUES (
#{objid}
, #{menu_type}
@@ -348,6 +349,7 @@
, #{lang_key}
, #{lang_key_desc}
, #{menu_icon}
, #{user_id}
)
</insert>
@@ -382,6 +384,7 @@
WHERE OBJID = #{menu_id}
</update>
<!-- ================================================================
사용자 관리
================================================================ -->
@@ -2,117 +2,134 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dashboard">
<!-- ═══════════════════════════════════════════════════════════════
대시보드 = 사용자 메뉴 (MENU_INFO 단일 본체)
- MENU_TYPE='1' AND MENU_URL ~ '^/\d+$' (회사별 시퀀스 숫자 URL) 인 row 가 대시보드
- 개인 대시보드: USER_ID = 로그인 사용자
- 공용 대시보드: USER_ID IS NULL (같은 COMPANY_CODE 공유)
═══════════════════════════════════════════════════════════════ -->
<!-- ═══ 대시보드 CRUD ═══ -->
<select id="getDashboardList" parameterType="map" resultType="map">
SELECT DASHBOARD_ID
, NAME
, ICON
, DISPLAY_ORDER
SELECT OBJID
, OBJID AS DASHBOARD_ID
, MENU_NAME_KOR AS NAME
, MENU_URL
, MENU_ICON AS ICON
, SEQ AS DISPLAY_ORDER
, COMPANY_CODE
, USER_ID
, IS_ACTIVE
, CREATED_BY
, STATUS
, WRITER AS CREATED_BY
, CREATED_DATE
, UPDATED_BY
, UPDATED_DATE
FROM DASHBOARDS
WHERE IS_ACTIVE = 'Y'
FROM MENU_INFO
WHERE MENU_TYPE = '1'
AND MENU_URL ~ '^/\d+$'
AND STATUS = 'active'
<include refid="common.companyCodeFilter"/>
AND (USER_ID = #{user_id} OR USER_ID IS NULL)
<if test='keyword != null and keyword != ""'>
AND NAME LIKE CONCAT('%', #{keyword}, '%')
AND MENU_NAME_KOR LIKE CONCAT('%', #{keyword}, '%')
</if>
ORDER BY DISPLAY_ORDER ASC, CREATED_DATE ASC
ORDER BY CAST(COALESCE(NULLIF(SEQ, ''), '0') AS INTEGER) ASC, CREATED_DATE ASC
<include refid="common.pagination"/>
</select>
<select id="getDashboardListCnt" parameterType="map" resultType="int">
SELECT COUNT(*)
FROM DASHBOARDS
WHERE IS_ACTIVE = 'Y'
FROM MENU_INFO
WHERE MENU_TYPE = '1'
AND MENU_URL ~ '^/\d+$'
AND STATUS = 'active'
<include refid="common.companyCodeFilter"/>
AND (USER_ID = #{user_id} OR USER_ID IS NULL)
<if test='keyword != null and keyword != ""'>
AND NAME LIKE CONCAT('%', #{keyword}, '%')
AND MENU_NAME_KOR LIKE CONCAT('%', #{keyword}, '%')
</if>
</select>
<select id="getDashboardInfo" parameterType="map" resultType="map">
SELECT DASHBOARD_ID
, NAME
, ICON
, DISPLAY_ORDER
SELECT OBJID
, OBJID AS DASHBOARD_ID
, MENU_NAME_KOR AS NAME
, MENU_URL
, MENU_ICON AS ICON
, SEQ AS DISPLAY_ORDER
, COMPANY_CODE
, USER_ID
, IS_ACTIVE
, CREATED_BY
, STATUS
, WRITER AS CREATED_BY
, CREATED_DATE
, UPDATED_BY
, UPDATED_DATE
FROM DASHBOARDS
WHERE DASHBOARD_ID = #{dashboard_id}
AND IS_ACTIVE = 'Y'
FROM MENU_INFO
WHERE OBJID = #{objid}
AND MENU_TYPE = '1'
AND STATUS = 'active'
</select>
<!-- 회사별 다음 메뉴 시퀀스 (URL 식별자로 사용) -->
<select id="getNextMenuSeq" parameterType="map" resultType="int">
SELECT COALESCE(MAX(CAST(NULLIF(SEQ, '') AS INTEGER)), 0) + 1
FROM MENU_INFO
WHERE MENU_TYPE = '1'
AND COMPANY_CODE = #{company_code}
AND MENU_URL ~ '^/\d+$'
</select>
<insert id="insertDashboard" parameterType="map">
INSERT INTO DASHBOARDS (
DASHBOARD_ID
, NAME
, ICON
, DISPLAY_ORDER
, COMPANY_CODE
, USER_ID
, IS_ACTIVE
, CREATED_BY
INSERT INTO MENU_INFO (
OBJID
, MENU_TYPE
, PARENT_OBJ_ID
, MENU_NAME_KOR
, MENU_URL
, SEQ
, WRITER
, CREATED_DATE
, UPDATED_BY
, UPDATED_DATE
, STATUS
, COMPANY_CODE
, MENU_ICON
, USER_ID
) VALUES (
#{dashboard_id}
#{objid}
, '1'
, '0'
, #{name}
, #{icon}
, #{display_order}
, #{menu_url}
, #{seq}
, #{writer}
, NOW()
, 'active'
, #{company_code}
, COALESCE(#{icon}, '📋')
, #{user_id}
, 'Y'
, #{user_id}
, CURRENT_TIMESTAMP
, #{user_id}
, CURRENT_TIMESTAMP
)
</insert>
<update id="updateDashboard" parameterType="map">
UPDATE DASHBOARDS
SET UPDATED_DATE = CURRENT_TIMESTAMP
, UPDATED_BY = #{user_id}
<if test='name != null'>
, NAME = #{name}
</if>
<if test='icon != null'>
, ICON = #{icon}
</if>
<if test='display_order != null'>
, DISPLAY_ORDER = #{display_order}
</if>
WHERE DASHBOARD_ID = #{dashboard_id}
AND IS_ACTIVE = 'Y'
UPDATE MENU_INFO
SET
<if test='name != null'>MENU_NAME_KOR = #{name},</if>
<if test='icon != null'>MENU_ICON = #{icon},</if>
<if test='display_order != null'>SEQ = #{display_order},</if>
OBJID = OBJID
WHERE OBJID = #{objid}
AND MENU_TYPE = '1'
</update>
<update id="deleteDashboard" parameterType="map">
UPDATE DASHBOARDS
SET IS_ACTIVE = 'D'
, UPDATED_DATE = CURRENT_TIMESTAMP
, UPDATED_BY = #{user_id}
WHERE DASHBOARD_ID = #{dashboard_id}
UPDATE MENU_INFO
SET STATUS = 'inactive'
WHERE OBJID = #{objid}
AND MENU_TYPE = '1'
</update>
<!-- ═══ 대시보드 카드 ═══ -->
<select id="getDashboardCardList" parameterType="map" resultType="map">
SELECT DC.CARD_ID
, DC.DASHBOARD_ID
, DC.MENU_OBJID
, DC.MENU_OBJID AS DASHBOARD_ID
, DC.TEMPLATE_ID
, DC.POSITION_X
, DC.POSITION_Y
@@ -130,15 +147,15 @@
, T.STATUS AS TEMPLATE_STATUS
FROM DASHBOARD_CARDS DC
LEFT JOIN TEMPLATES T ON DC.TEMPLATE_ID = T.TEMPLATE_ID AND T.IS_ACTIVE = 'Y'
WHERE DC.DASHBOARD_ID = #{dashboard_id}
AND DC.IS_ACTIVE = 'Y'
WHERE DC.MENU_OBJID = #{menu_objid}
AND DC.IS_ACTIVE = 'Y'
ORDER BY DC.DISPLAY_ORDER ASC, DC.CREATED_DATE ASC
</select>
<insert id="insertDashboardCard" parameterType="map">
INSERT INTO DASHBOARD_CARDS (
CARD_ID
, DASHBOARD_ID
, MENU_OBJID
, TEMPLATE_ID
, POSITION_X
, POSITION_Y
@@ -151,7 +168,7 @@
, UPDATED_DATE
) VALUES (
#{card_id}
, #{dashboard_id}
, #{menu_objid}
, #{template_id}
, #{position_x}
, #{position_y}
@@ -187,7 +204,7 @@
, DISPLAY_ORDER = #{display_order}
</if>
WHERE CARD_ID = #{card_id}
AND IS_ACTIVE = 'Y'
AND IS_ACTIVE = 'Y'
</update>
<update id="updateCardPosition" parameterType="map">
@@ -199,7 +216,7 @@
, IS_COLLAPSED = #{is_collapsed}
, UPDATED_DATE = CURRENT_TIMESTAMP
WHERE CARD_ID = #{card_id}
AND IS_ACTIVE = 'Y'
AND IS_ACTIVE = 'Y'
</update>
<update id="deleteDashboardCard" parameterType="map">
@@ -209,18 +226,4 @@
WHERE CARD_ID = #{card_id}
</update>
<!-- ═══ 사이드바 메뉴 ═══ -->
<select id="getSidebarMenu" parameterType="map" resultType="map">
SELECT DASHBOARD_ID
, NAME
, ICON
, DISPLAY_ORDER
FROM DASHBOARDS
WHERE IS_ACTIVE = 'Y'
<include refid="common.companyCodeFilter"/>
AND (USER_ID = #{user_id} OR USER_ID IS NULL)
ORDER BY DISPLAY_ORDER ASC, CREATED_DATE ASC
</select>
</mapper>