diff --git a/frontend/app/(main)/admin/systemMng/tableMngList/page.tsx b/frontend/app/(main)/admin/systemMng/tableMngList/page.tsx
index 44051e28..d98c2115 100644
--- a/frontend/app/(main)/admin/systemMng/tableMngList/page.tsx
+++ b/frontend/app/(main)/admin/systemMng/tableMngList/page.tsx
@@ -217,10 +217,16 @@ export default function TableManagementPage() {
// 메모이제이션된 입력타입 옵션
const memoizedInputTypeOptions = useMemo(() => inputTypeOptions, []);
- // 참조 테이블 옵션 (실제 테이블 목록에서 가져옴)
+ // 참조 테이블 옵션 (한글라벨 (영어명) 동시 표시)
const referenceTableOptions = [
{ value: "none", label: getTextFromUI(TABLE_MANAGEMENT_KEYS.LABEL_NONE, "선택 안함") },
- ...tables.map((table) => ({ value: table.tableName, label: table.displayName || table.tableName })),
+ ...tables.map((table) => ({
+ value: table.tableName,
+ label:
+ table.displayName && table.displayName !== table.tableName
+ ? `${table.displayName} (${table.tableName})`
+ : table.tableName,
+ })),
];
// 공통 코드 카테고리 목록 상태
@@ -1596,6 +1602,8 @@ export default function TableManagementPage() {
onIndexToggle={(columnName, checked) =>
handleIndexToggle(columnName, "index", checked)
}
+ tables={tables}
+ referenceTableColumns={referenceTableColumns}
/>
>
)}
@@ -1795,11 +1803,16 @@ export default function TableManagementPage() {
변경될 PK 컬럼:
{pendingPkColumns.length > 0 ? (
- {pendingPkColumns.map((col) => (
-
- {col}
-
- ))}
+ {pendingPkColumns.map((col) => {
+ const colInfo = columns.find((c) => c.columnName === col);
+ return (
+
+ {colInfo?.displayName && colInfo.displayName !== col
+ ? `${colInfo.displayName} (${col})`
+ : col}
+
+ );
+ })}
) : (
PK가 모두 제거됩니다
diff --git a/frontend/components/admin/table-type/ColumnDetailPanel.tsx b/frontend/components/admin/table-type/ColumnDetailPanel.tsx
index 77f5dedf..5e4e2a07 100644
--- a/frontend/components/admin/table-type/ColumnDetailPanel.tsx
+++ b/frontend/components/admin/table-type/ColumnDetailPanel.tsx
@@ -78,7 +78,16 @@ export function ColumnDetailPanel({
const refTableOpts = referenceTableOptions.length
? referenceTableOptions
- : [{ value: "none", label: "선택 안함" }, ...tables.map((t) => ({ value: t.tableName, label: t.displayName || t.tableName }))];
+ : [
+ { value: "none", label: "선택 안함" },
+ ...tables.map((t) => ({
+ value: t.tableName,
+ label:
+ t.displayName && t.displayName !== t.tableName
+ ? `${t.displayName} (${t.tableName})`
+ : t.tableName,
+ })),
+ ];
return (
@@ -90,7 +99,11 @@ export function ColumnDetailPanel({
{typeConf.label}
)}
- {column.columnName}
+
+ {column.displayName && column.displayName !== column.columnName
+ ? `${column.displayName} (${column.columnName})`
+ : column.columnName}
+
@@ -245,7 +263,13 @@ export function ColumnDetailPanel({
column.referenceColumn === refCol.columnName ? "opacity-100" : "opacity-0",
)}
/>
- {refCol.columnName}
+
+
+ {refCol.displayName && refCol.displayName !== refCol.columnName
+ ? `${refCol.displayName} (${refCol.columnName})`
+ : refCol.columnName}
+
+
))}
@@ -259,12 +283,20 @@ export function ColumnDetailPanel({
{/* 참조 요약 미니맵 */}
{column.referenceTable && column.referenceTable !== "none" && column.referenceColumn && (
-
- {column.referenceTable}
+
+ {(() => {
+ const tbl = refTableOpts.find((o) => o.value === column.referenceTable);
+ return tbl?.label ?? column.referenceTable;
+ })()}
→
-
- {column.referenceColumn}
+
+ {(() => {
+ const col = refColumns.find((c) => c.columnName === column.referenceColumn);
+ return col?.displayName && col.displayName !== column.referenceColumn
+ ? `${col.displayName} (${column.referenceColumn})`
+ : column.referenceColumn;
+ })()}
)}
diff --git a/frontend/components/admin/table-type/ColumnGrid.tsx b/frontend/components/admin/table-type/ColumnGrid.tsx
index c03c7516..825dbd36 100644
--- a/frontend/components/admin/table-type/ColumnGrid.tsx
+++ b/frontend/components/admin/table-type/ColumnGrid.tsx
@@ -5,8 +5,9 @@ import { MoreHorizontal, Database, Layers, FileStack } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
-import type { ColumnTypeInfo } from "./types";
+import type { ColumnTypeInfo, TableInfo } from "./types";
import { INPUT_TYPE_COLORS, getColumnGroup } from "./types";
+import type { ReferenceTableColumn } from "@/lib/api/entityJoin";
export interface ColumnGridConstraints {
primaryKey: { columns: string[] };
@@ -23,6 +24,9 @@ export interface ColumnGridProps {
getColumnIndexState?: (columnName: string) => { isPk: boolean; hasIndex: boolean };
onPkToggle?: (columnName: string, checked: boolean) => void;
onIndexToggle?: (columnName: string, checked: boolean) => void;
+ /** 호버 시 한글 라벨 표시용 (Badge title) */
+ tables?: TableInfo[];
+ referenceTableColumns?: Record;
}
function getIndexState(
@@ -53,6 +57,8 @@ export function ColumnGrid({
getColumnIndexState: externalGetIndexState,
onPkToggle,
onIndexToggle,
+ tables,
+ referenceTableColumns,
}: ColumnGridProps) {
const getIdxState = useMemo(
() => externalGetIndexState ?? ((name: string) => getIndexState(name, constraints)),
@@ -136,13 +142,12 @@ export function ColumnGrid({
{/* 4px 색상바 (타입별 진한 색) */}
- {/* 라벨 + 컬럼명 */}
+ {/* 라벨 + 컬럼명 (한글라벨 (영어명) 동시 표시) */}
- {column.displayName || column.columnName}
-
-
- {column.columnName}
+ {column.displayName && column.displayName !== column.columnName
+ ? `${column.displayName} (${column.columnName})`
+ : column.columnName}
@@ -150,11 +155,38 @@ export function ColumnGrid({
{column.inputType === "entity" && column.referenceTable && column.referenceTable !== "none" && (
<>
-
+ {
+ const t = tables.find((tb) => tb.tableName === column.referenceTable);
+ return t?.displayName && t.displayName !== t.tableName
+ ? `${t.displayName} (${column.referenceTable})`
+ : column.referenceTable;
+ })()
+ : column.referenceTable
+ }
+ >
{column.referenceTable}
→
-
+ {
+ const refCols = referenceTableColumns[column.referenceTable];
+ const c = refCols.find((rc) => rc.columnName === (column.referenceColumn ?? ""));
+ return c?.displayName && c.displayName !== c.columnName
+ ? `${c.displayName} (${column.referenceColumn})`
+ : column.referenceColumn ?? "—";
+ })()
+ : column.referenceColumn ?? "—"
+ }
+ >
{column.referenceColumn || "—"}
>
diff --git a/frontend/components/layout/AppLayout.tsx b/frontend/components/layout/AppLayout.tsx
index c80cb581..d2f13c79 100644
--- a/frontend/components/layout/AppLayout.tsx
+++ b/frontend/components/layout/AppLayout.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useState, Suspense, useEffect } from "react";
+import { useState, Suspense, useEffect, useCallback } from "react";
import { useRouter, usePathname, useSearchParams } from "next/navigation";
import { Button } from "@/components/ui/button";
import {
@@ -341,6 +341,10 @@ function AppLayoutInner({ children }: AppLayoutProps) {
const currentMenus = isAdminMode ? adminMenus : userMenus;
+ const currentTabs = useTabStore((s) => s[s.mode].tabs);
+ const currentActiveTabId = useTabStore((s) => s[s.mode].activeTabId);
+ const activeTab = currentTabs.find((t) => t.id === currentActiveTabId);
+
const toggleMenu = (menuId: string) => {
const newExpanded = new Set(expandedMenus);
if (newExpanded.has(menuId)) {
@@ -478,6 +482,26 @@ function AppLayoutInner({ children }: AppLayoutProps) {
}
};
+ // pathname + 활성 탭 기반 활성 메뉴 판별 (탭 네비게이션에서도 사이드바 활성 표시)
+ const isMenuActive = useCallback(
+ (menu: any): boolean => {
+ if (pathname === menu.url) return true;
+ if (!activeTab) return false;
+
+ const menuObjid = parseInt((menu.objid || menu.id)?.toString() || "0");
+
+ if (activeTab.type === "admin" && activeTab.adminUrl) {
+ return menu.url === activeTab.adminUrl;
+ }
+ if (activeTab.type === "screen") {
+ if (activeTab.menuObjid != null && menuObjid === activeTab.menuObjid) return true;
+ if (activeTab.screenId != null && menu.screenId === activeTab.screenId) return true;
+ }
+ return false;
+ },
+ [pathname, activeTab],
+ );
+
// 메뉴 트리 렌더링 (기존 MainLayout 스타일 적용)
const renderMenu = (menu: any, level: number = 0) => {
const isExpanded = expandedMenus.has(menu.id);
@@ -489,8 +513,8 @@ function AppLayoutInner({ children }: AppLayoutProps) {
draggable={isLeaf}
onDragStart={(e) => handleMenuDragStart(e, menu)}
className={`group flex min-h-[44px] cursor-pointer items-center justify-between rounded-md px-3 py-2 text-sm font-medium transition-colors duration-150 ease-in-out sm:min-h-[40px] ${
- pathname === menu.url
- ? "border-primary bg-primary/8 text-primary border-l-3 font-semibold"
+ isMenuActive(menu)
+ ? "border-l-[3px] border-l-primary bg-primary/10 dark:bg-primary/15 text-primary font-semibold"
: isExpanded
? "bg-accent/60 text-foreground"
: "text-muted-foreground hover:bg-accent hover:text-foreground"
@@ -518,8 +542,8 @@ function AppLayoutInner({ children }: AppLayoutProps) {
draggable={!child.hasChildren}
onDragStart={(e) => handleMenuDragStart(e, child)}
className={`flex min-h-[44px] cursor-pointer items-center rounded-md px-3 py-2 text-sm transition-colors duration-150 hover:cursor-pointer sm:min-h-[40px] ${
- pathname === child.url
- ? "border-primary bg-primary/8 text-primary border-l-3 font-semibold"
+ isMenuActive(child)
+ ? "border-l-[3px] border-l-primary bg-primary/10 dark:bg-primary/15 text-primary font-semibold"
: "text-muted-foreground hover:bg-accent hover:text-foreground"
}`}
onClick={() => handleMenuClick(child)}
@@ -557,6 +581,28 @@ function AppLayoutInner({ children }: AppLayoutProps) {
const uiMenus = convertMenuToUI(currentMenus, user as ExtendedUserInfo);
+ // 활성 탭에 해당하는 메뉴가 속한 부모 메뉴 자동 확장
+ useEffect(() => {
+ if (!activeTab || uiMenus.length === 0) return;
+
+ const toExpand: string[] = [];
+ for (const menu of uiMenus) {
+ if (menu.hasChildren && menu.children) {
+ const hasActiveChild = menu.children.some((child: any) => isMenuActive(child));
+ if (hasActiveChild && !expandedMenus.has(menu.id)) {
+ toExpand.push(menu.id);
+ }
+ }
+ }
+ if (toExpand.length > 0) {
+ setExpandedMenus((prev) => {
+ const next = new Set(prev);
+ toExpand.forEach((id) => next.add(id));
+ return next;
+ });
+ }
+ }, [activeTab, uiMenus, isMenuActive, expandedMenus]);
+
return (
{/* 모바일 헤더 */}
diff --git a/frontend/components/table-category/CategoryColumnList.tsx b/frontend/components/table-category/CategoryColumnList.tsx
index 2aed73fd..1b1bf0a5 100644
--- a/frontend/components/table-category/CategoryColumnList.tsx
+++ b/frontend/components/table-category/CategoryColumnList.tsx
@@ -5,6 +5,7 @@ import { apiClient } from "@/lib/api/client";
import { getCategoryValues } from "@/lib/api/tableCategoryValue";
import { ChevronRight, FolderTree, Loader2, Search, X } from "lucide-react";
import { Input } from "@/components/ui/input";
+import { cn } from "@/lib/utils";
export interface CategoryColumn {
tableName: string;
diff --git a/frontend/components/ui/tabs.tsx b/frontend/components/ui/tabs.tsx
index 95fdc082..568d44ed 100644
--- a/frontend/components/ui/tabs.tsx
+++ b/frontend/components/ui/tabs.tsx
@@ -26,7 +26,7 @@ function TabsList({
className={cn(
"px-3 py-1 text-sm font-medium transition-colors",
activeTabIndex === 0
- ? "text-foreground border-b-2 border-primary"
- : "text-muted-foreground hover:text-foreground"
+ ? "text-primary border-b-2 border-primary font-semibold bg-primary/5"
+ : "text-foreground/70 hover:text-foreground hover:bg-muted/30"
)}
>
{componentConfig.rightPanel?.title || "기본"}
@@ -3994,8 +3994,8 @@ export const SplitPanelLayoutComponent: React.FC
className={cn(
"px-3 py-1 text-sm font-medium transition-colors",
activeTabIndex === index + 1
- ? "text-foreground border-b-2 border-primary"
- : "text-muted-foreground hover:text-foreground"
+ ? "text-primary border-b-2 border-primary font-semibold bg-primary/5"
+ : "text-foreground/70 hover:text-foreground hover:bg-muted/30"
)}
>
{tab.label || `탭 ${index + 1}`}
diff --git a/frontend/lib/registry/components/v2-tabs-widget/tabs-component.tsx b/frontend/lib/registry/components/v2-tabs-widget/tabs-component.tsx
index ac6b208e..03de3cc1 100644
--- a/frontend/lib/registry/components/v2-tabs-widget/tabs-component.tsx
+++ b/frontend/lib/registry/components/v2-tabs-widget/tabs-component.tsx
@@ -48,8 +48,8 @@ const TabsDesignEditor: React.FC<{
return cn(
"px-4 py-2 text-sm font-medium cursor-pointer transition-colors",
isActive
- ? "bg-background border-b-2 border-primary text-primary"
- : "text-muted-foreground hover:text-foreground hover:bg-muted/50"
+ ? "bg-primary/10 border-b-2 border-primary text-primary font-semibold"
+ : "text-foreground/70 hover:text-foreground hover:bg-muted/50"
);
};
@@ -283,7 +283,7 @@ const TabsDesignEditor: React.FC<{
return (
{/* 탭 헤더 */}
-
+
{tabs.length > 0 ? (
tabs.map((tab) => (
{/* 탭 헤더 */}
-
+
{tabs.length > 0 ? (
tabs.map((tab) => (