[agent-pipeline] pipe-20260317084014-ydap round-1

This commit is contained in:
DDD1542 2026-03-17 18:05:10 +09:00
parent 9409f1308f
commit d3acf391a4
8 changed files with 164 additions and 40 deletions

View File

@ -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() {
<p className="text-sm font-medium"> PK :</p>
{pendingPkColumns.length > 0 ? (
<div className="mt-2 flex flex-wrap gap-2">
{pendingPkColumns.map((col) => (
<Badge key={col} variant="secondary" className="font-mono text-xs">
{col}
</Badge>
))}
{pendingPkColumns.map((col) => {
const colInfo = columns.find((c) => c.columnName === col);
return (
<Badge key={col} variant="secondary" className="text-xs">
{colInfo?.displayName && colInfo.displayName !== col
? `${colInfo.displayName} (${col})`
: col}
</Badge>
);
})}
</div>
) : (
<p className="text-destructive mt-2 text-sm">PK가 </p>

View File

@ -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 (
<div className="flex h-full w-full flex-col border-l bg-card">
@ -90,7 +99,11 @@ export function ColumnDetailPanel({
{typeConf.label}
</span>
)}
<span className="truncate font-mono text-sm font-medium">{column.columnName}</span>
<span className="truncate text-sm font-medium">
{column.displayName && column.displayName !== column.columnName
? `${column.displayName} (${column.columnName})`
: column.columnName}
</span>
</div>
<Button type="button" variant="ghost" size="icon" className="h-8 w-8 shrink-0" onClick={onClose} aria-label="닫기">
<X className="h-4 w-4" />
@ -207,7 +220,12 @@ export function ColumnDetailPanel({
className="h-9 w-full justify-between text-xs"
>
{column.referenceColumn && column.referenceColumn !== "none"
? column.referenceColumn
? (() => {
const matched = refColumns.find((c) => c.columnName === column.referenceColumn);
return matched?.displayName && matched.displayName !== column.referenceColumn
? `${matched.displayName} (${column.referenceColumn})`
: column.referenceColumn;
})()
: "컬럼 선택..."}
<ChevronsUpDown className="ml-2 h-3 w-3 opacity-50" />
</Button>
@ -245,7 +263,13 @@ export function ColumnDetailPanel({
column.referenceColumn === refCol.columnName ? "opacity-100" : "opacity-0",
)}
/>
{refCol.columnName}
<div className="flex flex-col">
<span className="font-medium">
{refCol.displayName && refCol.displayName !== refCol.columnName
? `${refCol.displayName} (${refCol.columnName})`
: refCol.columnName}
</span>
</div>
</CommandItem>
))}
</CommandGroup>
@ -259,12 +283,20 @@ export function ColumnDetailPanel({
{/* 참조 요약 미니맵 */}
{column.referenceTable && column.referenceTable !== "none" && column.referenceColumn && (
<div className="flex items-center gap-2 rounded-md bg-violet-50 px-3 py-2">
<span className="font-mono text-[11px] font-semibold text-violet-600">
{column.referenceTable}
<span className="text-[11px] font-semibold text-violet-600">
{(() => {
const tbl = refTableOpts.find((o) => o.value === column.referenceTable);
return tbl?.label ?? column.referenceTable;
})()}
</span>
<span className="text-muted-foreground text-[10px]"></span>
<span className="font-mono text-[11px] font-semibold text-violet-600">
{column.referenceColumn}
<span className="text-[11px] font-semibold text-violet-600">
{(() => {
const col = refColumns.find((c) => c.columnName === column.referenceColumn);
return col?.displayName && col.displayName !== column.referenceColumn
? `${col.displayName} (${column.referenceColumn})`
: column.referenceColumn;
})()}
</span>
</div>
)}

View File

@ -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<string, ReferenceTableColumn[]>;
}
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 색상바 (타입별 진한 색) */}
<div className={cn("h-full min-h-8 w-1 rounded-full", typeConf.barColor)} />
{/* 라벨 + 컬럼명 */}
{/* 라벨 + 컬럼명 (한글라벨 (영어명) 동시 표시) */}
<div className="min-w-0">
<div className="truncate text-sm font-medium">
{column.displayName || column.columnName}
</div>
<div className="truncate font-mono text-xs text-muted-foreground">
{column.columnName}
{column.displayName && column.displayName !== column.columnName
? `${column.displayName} (${column.columnName})`
: column.columnName}
</div>
</div>
@ -150,11 +155,38 @@ export function ColumnGrid({
<div className="flex min-w-0 flex-wrap gap-1">
{column.inputType === "entity" && column.referenceTable && column.referenceTable !== "none" && (
<>
<Badge variant="outline" className="text-xs font-normal">
<Badge
variant="outline"
className="text-xs font-normal"
title={
tables
? (() => {
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}
</Badge>
<span className="text-muted-foreground text-xs"></span>
<Badge variant="outline" className="text-xs font-normal">
<Badge
variant="outline"
className="text-xs font-normal"
title={
referenceTableColumns?.[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 || "—"}
</Badge>
</>

View File

@ -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 (
<div className="bg-background flex h-screen flex-col">
{/* 모바일 헤더 */}

View File

@ -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;

View File

@ -26,7 +26,7 @@ function TabsList({
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
"bg-muted/30 text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
"bg-muted/50 text-muted-foreground inline-flex h-10 w-fit items-center justify-center rounded-lg border border-border/50 p-1",
className
)}
{...props}
@ -42,7 +42,7 @@ function TabsTrigger({
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"data-[state=active]:bg-background data-[state=active]:font-semibold dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-foreground/70 inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}

View File

@ -3981,8 +3981,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
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<SplitPanelLayoutComponentProps>
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}`}

View File

@ -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 (
<div className="flex h-full w-full flex-col overflow-hidden rounded-lg border bg-background">
{/* 탭 헤더 */}
<div className="flex items-center border-b bg-muted/30">
<div className="flex items-center border-b bg-muted/50">
{tabs.length > 0 ? (
tabs.map((tab) => (
<div
@ -649,8 +649,8 @@ ComponentRegistry.registerComponent({
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"
);
};
@ -662,7 +662,7 @@ ComponentRegistry.registerComponent({
onDragEnd={onDragEnd}
>
{/* 탭 헤더 */}
<div className="flex items-center border-b bg-muted/30">
<div className="flex items-center border-b bg-muted/50">
{tabs.length > 0 ? (
tabs.map((tab) => (
<div