[agent-pipeline] pipe-20260315131310-l8kw round-1

This commit is contained in:
DDD1542 2026-03-15 22:19:35 +09:00
parent 8ed7faf517
commit 232650bc07
2 changed files with 175 additions and 62 deletions

View File

@ -2058,6 +2058,119 @@ export const getScreenSubTables = async (req: AuthenticatedRequest, res: Respons
});
});
// 6. v2-repeater 컴포넌트에서 selectedTable/foreignKey 추출
const v2RepeaterQuery = `
SELECT DISTINCT
sd.screen_id,
sd.screen_name,
sd.table_name as main_table,
comp->'overrides'->>'type' as component_type,
comp->'overrides'->>'selectedTable' as sub_table,
comp->'overrides'->>'foreignKey' as foreign_key,
comp->'overrides'->>'parentTable' as parent_table
FROM screen_definitions sd
JOIN screen_layouts_v2 slv2 ON sd.screen_id = slv2.screen_id,
jsonb_array_elements(slv2.layout_data->'components') as comp
WHERE sd.screen_id = ANY($1)
AND comp->'overrides'->>'type' = 'v2-repeater'
AND comp->'overrides'->>'selectedTable' IS NOT NULL
`;
const v2RepeaterResult = await pool.query(v2RepeaterQuery, [screenIds]);
v2RepeaterResult.rows.forEach((row: any) => {
const screenId = row.screen_id;
const mainTable = row.main_table;
const subTable = row.sub_table;
const foreignKey = row.foreign_key;
if (!subTable || subTable === mainTable) return;
if (!screenSubTables[screenId]) {
screenSubTables[screenId] = {
screenId,
screenName: row.screen_name,
mainTable: mainTable || '',
subTables: [],
};
}
const exists = screenSubTables[screenId].subTables.some(
(st) => st.tableName === subTable
);
if (!exists) {
screenSubTables[screenId].subTables.push({
tableName: subTable,
componentType: 'v2-repeater',
relationType: 'rightPanelRelation',
fieldMappings: foreignKey ? [{
sourceField: 'id',
targetField: foreignKey,
sourceDisplayName: 'ID',
targetDisplayName: foreignKey,
}] : undefined,
});
}
});
logger.info("v2-repeater 서브 테이블 추출 완료", {
screenIds,
v2RepeaterCount: v2RepeaterResult.rows.length,
});
// 7. rightPanel.components 내부의 componentConfig.detailTable 추출 (v2-bom-tree 등)
const v2DetailTableQuery = `
SELECT DISTINCT
sd.screen_id,
sd.screen_name,
sd.table_name as main_table,
inner_comp->>'type' as component_type,
inner_comp->'componentConfig'->>'detailTable' as sub_table,
inner_comp->'componentConfig'->>'foreignKey' as foreign_key
FROM screen_definitions sd
JOIN screen_layouts_v2 slv2 ON sd.screen_id = slv2.screen_id,
jsonb_array_elements(slv2.layout_data->'components') as comp,
jsonb_array_elements(
COALESCE(
comp->'overrides'->'rightPanel'->'components',
comp->'overrides'->'leftPanel'->'components',
'[]'::jsonb
)
) as inner_comp
WHERE sd.screen_id = ANY($1)
AND inner_comp->'componentConfig'->>'detailTable' IS NOT NULL
`;
const v2DetailTableResult = await pool.query(v2DetailTableQuery, [screenIds]);
v2DetailTableResult.rows.forEach((row: any) => {
const screenId = row.screen_id;
const mainTable = row.main_table;
const subTable = row.sub_table;
const foreignKey = row.foreign_key;
if (!subTable || subTable === mainTable) return;
if (!screenSubTables[screenId]) {
screenSubTables[screenId] = {
screenId,
screenName: row.screen_name,
mainTable: mainTable || '',
subTables: [],
};
}
const exists = screenSubTables[screenId].subTables.some(
(st) => st.tableName === subTable
);
if (!exists) {
screenSubTables[screenId].subTables.push({
tableName: subTable,
componentType: row.component_type || 'v2-bom-tree',
relationType: 'rightPanelRelation',
fieldMappings: foreignKey ? [{
sourceField: 'id',
targetField: foreignKey,
sourceDisplayName: 'ID',
targetDisplayName: foreignKey,
}] : undefined,
});
}
});
logger.info("v2-bom-tree/detailTable 서브 테이블 추출 완료", {
screenIds,
v2DetailTableCount: v2DetailTableResult.rows.length,
});
// ============================================================
// 저장 테이블 정보 추출
// ============================================================

View File

@ -153,12 +153,12 @@ export const ScreenNode: React.FC<{ data: ScreenNodeData }> = ({ data }) => {
return (
<div
className={`group relative flex h-[240px] w-[240px] flex-col overflow-hidden rounded-[10px] border bg-card/80 backdrop-blur-sm transition-all cursor-pointer ${
className={`group relative flex h-[240px] w-[240px] flex-col overflow-hidden rounded-[10px] border bg-card dark:bg-card/80 backdrop-blur-sm transition-all cursor-pointer ${
isFocused
? "border-primary/40 shadow-[0_0_0_1px_hsl(var(--primary)/0.4)] scale-[1.03]"
: isFaded
? "opacity-40 border-border/10 shadow-[0_4px_24px_-8px_rgba(0,0,0,0.5)]"
: "border-border/10 shadow-[0_4px_24px_-8px_rgba(0,0,0,0.5)] hover:border-border/20 hover:shadow-[0_4px_24px_-8px_rgba(0,0,0,0.5)] hover:-translate-y-0.5"
? "opacity-40 border-border/40 dark:border-border/10 shadow-[0_4px_24px_-8px_rgba(0,0,0,0.08)] dark:shadow-[0_4px_24px_-8px_rgba(0,0,0,0.5)]"
: "border-border/40 dark:border-border/10 shadow-[0_4px_24px_-8px_rgba(0,0,0,0.08)] dark:shadow-[0_4px_24px_-8px_rgba(0,0,0,0.5)] hover:border-border/50 dark:hover:border-border/20 hover:shadow-[0_4px_24px_-8px_rgba(0,0,0,0.08)] dark:hover:shadow-[0_4px_24px_-8px_rgba(0,0,0,0.5)] hover:-translate-y-0.5"
}`}
style={{
filter: isFaded
@ -175,23 +175,23 @@ export const ScreenNode: React.FC<{ data: ScreenNodeData }> = ({ data }) => {
type="target"
position={Position.Left}
id="left"
className="!h-2 !w-2 !border-[1.5px] !border-card !bg-muted-foreground/40 opacity-0 transition-all duration-300 group-hover:opacity-100 group-hover:shadow-[0_0_6px_hsl(var(--primary)/0.5)]"
className="!h-2 !w-2 !border-[1.5px] !border-card !bg-muted-foreground/70 dark:!bg-muted-foreground/40 opacity-0 transition-all duration-300 group-hover:opacity-100 group-hover:shadow-[0_0_6px_hsl(var(--primary)/0.5)]"
/>
<Handle
type="source"
position={Position.Right}
id="right"
className="!h-2 !w-2 !border-[1.5px] !border-card !bg-muted-foreground/40 opacity-0 transition-all duration-300 group-hover:opacity-100 group-hover:shadow-[0_0_6px_hsl(var(--primary)/0.5)]"
className="!h-2 !w-2 !border-[1.5px] !border-card !bg-muted-foreground/70 dark:!bg-muted-foreground/40 opacity-0 transition-all duration-300 group-hover:opacity-100 group-hover:shadow-[0_0_6px_hsl(var(--primary)/0.5)]"
/>
<Handle
type="source"
position={Position.Bottom}
id="bottom"
className="!h-2 !w-2 !border-[1.5px] !border-card !bg-muted-foreground/40 opacity-0 transition-all duration-300 group-hover:opacity-100 group-hover:shadow-[0_0_6px_hsl(var(--primary)/0.5)]"
className="!h-2 !w-2 !border-[1.5px] !border-card !bg-muted-foreground/70 dark:!bg-muted-foreground/40 opacity-0 transition-all duration-300 group-hover:opacity-100 group-hover:shadow-[0_0_6px_hsl(var(--primary)/0.5)]"
/>
{/* 헤더: 그라디언트 제거, 모노크롬 */}
<div className="flex items-center gap-2 border-b border-border/10 bg-muted/30 px-3 py-2 transition-colors duration-300">
<div className="flex items-center gap-2 border-b border-border/40 dark:border-border/10 bg-muted/50 dark:bg-muted/30 px-3 py-2 transition-colors duration-300">
<div className="flex h-6 w-6 items-center justify-center rounded bg-primary/10 text-primary">
<Monitor className="h-3.5 w-3.5" />
</div>
@ -199,7 +199,7 @@ export const ScreenNode: React.FC<{ data: ScreenNodeData }> = ({ data }) => {
<div className="truncate text-xs font-bold text-foreground">{label}</div>
{tableName && <div className="truncate text-[9px] text-muted-foreground font-mono">{tableName}</div>}
</div>
{(isMain || isFocused) && <span className="flex h-2 w-2 rounded-full bg-foreground/8 animate-pulse" />}
{(isMain || isFocused) && <span className="flex h-2 w-2 rounded-full bg-foreground/[0.12] dark:bg-foreground/8 animate-pulse" />}
</div>
{/* 화면 미리보기 영역 (컴팩트) */}
@ -207,7 +207,7 @@ export const ScreenNode: React.FC<{ data: ScreenNodeData }> = ({ data }) => {
{layoutSummary ? (
<ScreenPreview layoutSummary={layoutSummary} screenType={screenType} />
) : (
<div className="flex h-full flex-col items-center justify-center text-muted-foreground/40">
<div className="flex h-full flex-col items-center justify-center text-muted-foreground/70 dark:text-muted-foreground/40">
{getScreenTypeIcon(screenType)}
<span className="mt-1 text-[10px]">: {label}</span>
</div>
@ -215,7 +215,7 @@ export const ScreenNode: React.FC<{ data: ScreenNodeData }> = ({ data }) => {
</div>
{/* 푸터 (타입 칩 + 컴포넌트 수) */}
<div className="flex items-center justify-between border-t border-border/10 bg-background/50 px-3 py-1.5">
<div className="flex items-center justify-between border-t border-border/40 dark:border-border/10 bg-background dark:bg-background/50 px-3 py-1.5">
<span className="text-[9px] font-medium px-[7px] py-[2px] rounded bg-primary/10 text-primary">{getScreenTypeLabel(screenType)}</span>
<span className="text-[9px] text-muted-foreground">{layoutSummary?.totalComponents ?? 0} </span>
</div>
@ -267,37 +267,37 @@ const ScreenPreview: React.FC<{ layoutSummary: ScreenLayoutSummary; screenType:
// 그리드 화면 일러스트 (모노크롬)
if (screenType === "grid") {
return (
<div className="flex h-full flex-col gap-2 rounded-lg border border-border/5 bg-muted/10 p-3">
<div className="flex h-full flex-col gap-2 rounded-lg border border-border/30 dark:border-border/5 bg-muted/30 dark:bg-muted/10 p-3">
{/* 상단 툴바 */}
<div className="flex items-center gap-2">
<div className="h-4 w-16 rounded bg-foreground/10" />
<div className="h-4 w-16 rounded bg-foreground/[0.15] dark:bg-foreground/10" />
<div className="flex-1" />
<div className="h-4 w-8 rounded bg-foreground/12" />
<div className="h-4 w-8 rounded bg-foreground/12" />
<div className="h-4 w-8 rounded bg-foreground/8" />
<div className="h-4 w-8 rounded bg-foreground/[0.18] dark:bg-foreground/12" />
<div className="h-4 w-8 rounded bg-foreground/[0.18] dark:bg-foreground/12" />
<div className="h-4 w-8 rounded bg-foreground/[0.12] dark:bg-foreground/8" />
</div>
{/* 테이블 헤더 */}
<div className="flex gap-1 rounded-t-md bg-foreground/12 px-2 py-2">
<div className="flex gap-1 rounded-t-md bg-foreground/[0.18] dark:bg-foreground/12 px-2 py-2">
{[...Array(5)].map((_, i) => (
<div key={i} className="h-2.5 flex-1 rounded bg-foreground/8" />
<div key={i} className="h-2.5 flex-1 rounded bg-foreground/[0.12] dark:bg-foreground/8" />
))}
</div>
{/* 테이블 행들 */}
<div className="flex flex-1 flex-col gap-1 overflow-hidden">
{[...Array(7)].map((_, i) => (
<div key={i} className={`flex gap-1 rounded px-2 py-1.5 ${i % 2 === 0 ? "bg-muted/10" : "bg-card"}`}>
<div key={i} className={`flex gap-1 rounded px-2 py-1.5 ${i % 2 === 0 ? "bg-muted/30 dark:bg-muted/10" : "bg-card"}`}>
{[...Array(5)].map((_, j) => (
<div key={j} className="h-2 flex-1 rounded bg-foreground/6" />
<div key={j} className="h-2 flex-1 rounded bg-foreground/[0.1] dark:bg-foreground/6" />
))}
</div>
))}
</div>
{/* 페이지네이션 */}
<div className="flex items-center justify-center gap-2 pt-1">
<div className="h-2.5 w-4 rounded bg-foreground/8" />
<div className="h-2.5 w-4 rounded bg-foreground/12" />
<div className="h-2.5 w-4 rounded bg-foreground/8" />
<div className="h-2.5 w-4 rounded bg-foreground/8" />
<div className="h-2.5 w-4 rounded bg-foreground/[0.12] dark:bg-foreground/8" />
<div className="h-2.5 w-4 rounded bg-foreground/[0.18] dark:bg-foreground/12" />
<div className="h-2.5 w-4 rounded bg-foreground/[0.12] dark:bg-foreground/8" />
<div className="h-2.5 w-4 rounded bg-foreground/[0.12] dark:bg-foreground/8" />
</div>
</div>
);
@ -306,18 +306,18 @@ const ScreenPreview: React.FC<{ layoutSummary: ScreenLayoutSummary; screenType:
// 폼 화면 일러스트 (모노크롬)
if (screenType === "form") {
return (
<div className="flex h-full flex-col gap-3 rounded-lg border border-border/5 bg-muted/10 p-3">
<div className="flex h-full flex-col gap-3 rounded-lg border border-border/30 dark:border-border/5 bg-muted/30 dark:bg-muted/10 p-3">
{/* 폼 필드들 */}
{[...Array(6)].map((_, i) => (
<div key={i} className="flex items-center gap-3">
<div className="h-2.5 w-14 rounded bg-foreground/8" />
<div className="h-5 flex-1 rounded-md border border-border/5 bg-card" />
<div className="h-2.5 w-14 rounded bg-foreground/[0.12] dark:bg-foreground/8" />
<div className="h-5 flex-1 rounded-md border border-border/30 dark:border-border/5 bg-card" />
</div>
))}
{/* 버튼 영역 */}
<div className="mt-auto flex justify-end gap-2 border-t border-border/5 pt-3">
<div className="h-5 w-14 rounded-md bg-foreground/8" />
<div className="h-5 w-14 rounded-md bg-foreground/12" />
<div className="mt-auto flex justify-end gap-2 border-t border-border/30 dark:border-border/5 pt-3">
<div className="h-5 w-14 rounded-md bg-foreground/[0.12] dark:bg-foreground/8" />
<div className="h-5 w-14 rounded-md bg-foreground/[0.18] dark:bg-foreground/12" />
</div>
</div>
);
@ -326,23 +326,23 @@ const ScreenPreview: React.FC<{ layoutSummary: ScreenLayoutSummary; screenType:
// 대시보드 화면 일러스트 (모노크롬)
if (screenType === "dashboard") {
return (
<div className="grid h-full grid-cols-2 gap-2 rounded-lg border border-border/5 bg-muted/10 p-3">
<div className="grid h-full grid-cols-2 gap-2 rounded-lg border border-border/30 dark:border-border/5 bg-muted/30 dark:bg-muted/10 p-3">
{/* 카드/차트들 */}
<div className="rounded-lg bg-foreground/5 p-2">
<div className="mb-2 h-2.5 w-10 rounded bg-foreground/10" />
<div className="h-10 rounded-md bg-foreground/8" />
<div className="rounded-lg bg-foreground/[0.08] dark:bg-foreground/5 p-2">
<div className="mb-2 h-2.5 w-10 rounded bg-foreground/[0.15] dark:bg-foreground/10" />
<div className="h-10 rounded-md bg-foreground/[0.12] dark:bg-foreground/8" />
</div>
<div className="rounded-lg bg-foreground/5 p-2">
<div className="mb-2 h-2.5 w-10 rounded bg-foreground/10" />
<div className="h-10 rounded-md bg-foreground/8" />
<div className="rounded-lg bg-foreground/[0.08] dark:bg-foreground/5 p-2">
<div className="mb-2 h-2.5 w-10 rounded bg-foreground/[0.15] dark:bg-foreground/10" />
<div className="h-10 rounded-md bg-foreground/[0.12] dark:bg-foreground/8" />
</div>
<div className="col-span-2 rounded-lg bg-foreground/5 p-2">
<div className="mb-2 h-2.5 w-12 rounded bg-foreground/10" />
<div className="col-span-2 rounded-lg bg-foreground/[0.08] dark:bg-foreground/5 p-2">
<div className="mb-2 h-2.5 w-12 rounded bg-foreground/[0.15] dark:bg-foreground/10" />
<div className="flex h-14 items-end gap-1">
{[...Array(10)].map((_, i) => (
<div
key={i}
className="flex-1 rounded-t bg-foreground/10"
className="flex-1 rounded-t bg-foreground/[0.15] dark:bg-foreground/10"
style={{ height: `${25 + Math.random() * 75}%` }}
/>
))}
@ -355,13 +355,13 @@ const ScreenPreview: React.FC<{ layoutSummary: ScreenLayoutSummary; screenType:
// 액션 화면 일러스트 (모노크롬)
if (screenType === "action") {
return (
<div className="flex h-full flex-col items-center justify-center gap-4 rounded-lg border border-border/5 bg-muted/10 p-3">
<div className="rounded-full bg-foreground/5 p-4 text-muted-foreground">
<div className="flex h-full flex-col items-center justify-center gap-4 rounded-lg border border-border/30 dark:border-border/5 bg-muted/30 dark:bg-muted/10 p-3">
<div className="rounded-full bg-foreground/[0.08] dark:bg-foreground/5 p-4 text-muted-foreground">
<MousePointer2 className="h-10 w-10" />
</div>
<div className="flex gap-3">
<div className="h-7 w-16 rounded-md bg-foreground/12" />
<div className="h-7 w-16 rounded-md bg-foreground/8" />
<div className="h-7 w-16 rounded-md bg-foreground/[0.18] dark:bg-foreground/12" />
<div className="h-7 w-16 rounded-md bg-foreground/[0.12] dark:bg-foreground/8" />
</div>
<div className="text-xs font-medium text-muted-foreground"> </div>
</div>
@ -370,8 +370,8 @@ const ScreenPreview: React.FC<{ layoutSummary: ScreenLayoutSummary; screenType:
// 기본 (알 수 없는 타입, 모노크롬)
return (
<div className="flex h-full flex-col items-center justify-center gap-3 rounded-lg border border-border/5 bg-muted/10 text-muted-foreground">
<div className="rounded-full bg-foreground/5 p-4">
<div className="flex h-full flex-col items-center justify-center gap-3 rounded-lg border border-border/30 dark:border-border/5 bg-muted/30 dark:bg-muted/10 text-muted-foreground">
<div className="rounded-full bg-foreground/[0.08] dark:bg-foreground/5 p-4">
{getScreenTypeIcon(screenType)}
</div>
<span className="text-sm font-medium">{totalComponents} </span>
@ -506,7 +506,7 @@ export const TableNode: React.FC<{ data: TableNodeData }> = ({ data }) => {
return (
<div
className={`group relative flex w-[260px] flex-col overflow-visible rounded-[10px] border bg-card/80 backdrop-blur-sm shadow-[0_4px_24px_-8px_rgba(0,0,0,0.5)] ${
className={`group relative flex w-[260px] flex-col overflow-visible rounded-[10px] border bg-card dark:bg-card/80 backdrop-blur-sm shadow-[0_4px_24px_-8px_rgba(0,0,0,0.08)] dark:shadow-[0_4px_24px_-8px_rgba(0,0,0,0.5)] ${
// 1. 필터 테이블 (마스터-디테일의 디테일 테이블)
isFilterTable
? "border-primary/30 shadow-[0_0_0_1px_hsl(var(--primary)/0.3)]"
@ -518,9 +518,9 @@ export const TableNode: React.FC<{ data: TableNodeData }> = ({ data }) => {
? "border-primary/30 shadow-[0_0_0_1px_hsl(var(--primary)/0.3),0_0_24px_-8px_hsl(var(--primary)/0.1)] bg-card"
// 4. 흐리게 처리
: isFaded
? "opacity-60 bg-card border-border/10"
? "opacity-60 bg-card border-border/40 dark:border-border/10"
// 5. 기본
: "border-border/10 hover:border-border/20"
: "border-border/40 dark:border-border/10 hover:border-border/50 dark:hover:border-border/20"
}`}
style={{
filter: isFaded ? "grayscale(80%)" : "none",
@ -548,7 +548,7 @@ export const TableNode: React.FC<{ data: TableNodeData }> = ({ data }) => {
type="target"
position={Position.Top}
id="top"
className="!h-2 !w-2 !border-[1.5px] !border-card !bg-muted-foreground/40 opacity-0 transition-opacity group-hover:opacity-100"
className="!h-2 !w-2 !border-[1.5px] !border-card !bg-muted-foreground/70 dark:!bg-muted-foreground/40 opacity-0 transition-opacity group-hover:opacity-100"
/>
{/* top source: 메인테이블 → 메인테이블 연결용 (위쪽으로 나가는 선) */}
<Handle
@ -556,25 +556,25 @@ export const TableNode: React.FC<{ data: TableNodeData }> = ({ data }) => {
position={Position.Top}
id="top_source"
style={{ top: -4 }}
className="!h-2 !w-2 !border-[1.5px] !border-card !bg-muted-foreground/40 opacity-0 transition-opacity group-hover:opacity-100"
className="!h-2 !w-2 !border-[1.5px] !border-card !bg-muted-foreground/70 dark:!bg-muted-foreground/40 opacity-0 transition-opacity group-hover:opacity-100"
/>
<Handle
type="target"
position={Position.Left}
id="left"
className="!h-2 !w-2 !border-[1.5px] !border-card !bg-muted-foreground/40 opacity-0 transition-opacity group-hover:opacity-100"
className="!h-2 !w-2 !border-[1.5px] !border-card !bg-muted-foreground/70 dark:!bg-muted-foreground/40 opacity-0 transition-opacity group-hover:opacity-100"
/>
<Handle
type="source"
position={Position.Right}
id="right"
className="!h-2 !w-2 !border-[1.5px] !border-card !bg-muted-foreground/40 opacity-0 transition-opacity group-hover:opacity-100"
className="!h-2 !w-2 !border-[1.5px] !border-card !bg-muted-foreground/70 dark:!bg-muted-foreground/40 opacity-0 transition-opacity group-hover:opacity-100"
/>
<Handle
type="source"
position={Position.Bottom}
id="bottom"
className="!h-2 !w-2 !border-[1.5px] !border-card !bg-muted-foreground/40 opacity-0 transition-opacity group-hover:opacity-100"
className="!h-2 !w-2 !border-[1.5px] !border-card !bg-muted-foreground/70 dark:!bg-muted-foreground/40 opacity-0 transition-opacity group-hover:opacity-100"
/>
{/* bottom target: 메인테이블 ← 메인테이블 연결용 (아래에서 들어오는 선) */}
<Handle
@ -582,18 +582,18 @@ export const TableNode: React.FC<{ data: TableNodeData }> = ({ data }) => {
position={Position.Bottom}
id="bottom_target"
style={{ bottom: -4 }}
className="!h-2 !w-2 !border-[1.5px] !border-card !bg-muted-foreground/40 opacity-0 transition-opacity group-hover:opacity-100"
className="!h-2 !w-2 !border-[1.5px] !border-card !bg-muted-foreground/70 dark:!bg-muted-foreground/40 opacity-0 transition-opacity group-hover:opacity-100"
/>
{/* 헤더: 그라디언트 제거, bg-muted/30 + 아이콘 박스 */}
<div className="flex items-center gap-2.5 px-3.5 py-2.5 border-b border-border/10 bg-muted/30 rounded-t-[10px] transition-colors duration-700 ease-in-out">
<div className="flex items-center gap-2.5 px-3.5 py-2.5 border-b border-border/40 dark:border-border/10 bg-muted/50 dark:bg-muted/30 rounded-t-[10px] transition-colors duration-700 ease-in-out">
<div className="flex h-7 w-7 items-center justify-center rounded-[7px] bg-cyan-500/10 shrink-0">
<Database className="h-3.5 w-3.5 text-cyan-400" />
</div>
<div className="flex-1 min-w-0">
<div className="truncate text-[11px] font-semibold text-foreground font-mono">{label}</div>
{/* 필터 관계에 따른 문구 변경 */}
<div className="truncate text-[9px] font-mono text-muted-foreground/40 tracking-[-0.3px]">
<div className="truncate text-[9px] font-mono text-muted-foreground/70 dark:text-muted-foreground/40 tracking-[-0.3px]">
{isFilterSource
? "마스터 테이블 (필터 소스)"
: hasFilterRelation
@ -602,7 +602,7 @@ export const TableNode: React.FC<{ data: TableNodeData }> = ({ data }) => {
</div>
</div>
{hasActiveColumns && (
<span className="text-[9px] font-mono text-muted-foreground/40 px-1.5 py-0.5 rounded bg-foreground/5 border border-border/10 tracking-[-0.3px] shrink-0">
<span className="text-[9px] font-mono text-muted-foreground/70 dark:text-muted-foreground/40 px-1.5 py-0.5 rounded bg-foreground/[0.08] dark:bg-foreground/5 border border-border/40 dark:border-border/10 tracking-[-0.3px] shrink-0">
{displayColumns.length} ref
</span>
)}
@ -747,7 +747,7 @@ export const TableNode: React.FC<{ data: TableNodeData }> = ({ data }) => {
)}
{/* 타입 */}
<span className="text-[8px] text-muted-foreground/30 font-mono tracking-[-0.3px]">{col.type}</span>
<span className="text-[8px] text-muted-foreground/60 dark:text-muted-foreground/30 font-mono tracking-[-0.3px]">{col.type}</span>
</div>
);
})}
@ -767,21 +767,21 @@ export const TableNode: React.FC<{ data: TableNodeData }> = ({ data }) => {
</div>
{/* 푸터: cols + PK/FK 카운트 */}
<div className="flex items-center justify-between border-t border-border/10 px-3.5 py-1.5 bg-background/50">
<span className="text-[9px] text-muted-foreground/40 font-mono tracking-[-0.3px]">
<div className="flex items-center justify-between border-t border-border/40 dark:border-border/10 px-3.5 py-1.5 bg-background dark:bg-background/50">
<span className="text-[9px] text-muted-foreground/70 dark:text-muted-foreground/40 font-mono tracking-[-0.3px]">
{hasActiveColumns ? `${displayColumns.length}/${totalCount}` : totalCount} cols
</span>
<div className="flex gap-2.5 text-[9px] font-mono tracking-[-0.3px]">
{columns?.some(c => c.isPrimaryKey) && (
<span className="flex items-center gap-1">
<span className="w-1 h-1 rounded-full bg-amber-400" />
<span className="text-muted-foreground/40">PK {columns.filter(c => c.isPrimaryKey).length}</span>
<span className="text-muted-foreground/70 dark:text-muted-foreground/40">PK {columns.filter(c => c.isPrimaryKey).length}</span>
</span>
)}
{columns?.some(c => c.isForeignKey) && (
<span className="flex items-center gap-1">
<span className="w-1 h-1 rounded-full bg-primary" />
<span className="text-muted-foreground/40">FK {columns.filter(c => c.isForeignKey).length}</span>
<span className="text-muted-foreground/70 dark:text-muted-foreground/40">FK {columns.filter(c => c.isForeignKey).length}</span>
</span>
)}
</div>