새로운 문서 파일을 추가하여 현재 사용 가능한 16개 컴포넌트의 다국어 지원 및 테이블 설정 기능 현황을 정리했습니다. 각 컴포넌트별 상세 현황과 우선순위 작업 목록을 포함하여 기능 적용 상태를 명확히 하였습니다.
This commit is contained in:
parent
e937ba9161
commit
57d86c8ef1
|
|
@ -0,0 +1,148 @@
|
|||
# 컴포넌트 기능 현황
|
||||
|
||||
> 작성일: 2026-01-15
|
||||
> 현재 사용 가능한 16개 컴포넌트의 다국어 지원 및 테이블 설정 기능 현황
|
||||
|
||||
---
|
||||
|
||||
## 요약
|
||||
|
||||
| 기능 | 적용 완료 | 미적용 | 해당없음 |
|
||||
|------|----------|--------|---------|
|
||||
| **다국어 지원** | 3개 | 10개 | 3개 |
|
||||
| **컴포넌트별 테이블 설정** | 4개 | 6개 | 6개 |
|
||||
|
||||
---
|
||||
|
||||
## 컴포넌트별 상세 현황
|
||||
|
||||
### 데이터 표시 (Display) - 4개
|
||||
|
||||
| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 |
|
||||
|---------|:----------:|:----------:|------|
|
||||
| **테이블 리스트** | ✅ 적용 | ✅ 적용 | `customTableName`, `useCustomTable` 지원 |
|
||||
| **카드 디스플레이** | ❌ 미적용 | ⚠️ 부분 | `screenTableName`만 사용, 컴포넌트별 테이블 선택 UI 없음 |
|
||||
| **텍스트 표시** | ❌ 미적용 | ➖ 해당없음 | 정적 텍스트 표시용 |
|
||||
| **피벗 그리드** | ❌ 미적용 | ⚠️ 부분 | `tableName` 설정 가능하나 Combobox UI 없음 |
|
||||
|
||||
---
|
||||
|
||||
### 데이터 입력 (Data) - 2개
|
||||
|
||||
| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 |
|
||||
|---------|:----------:|:----------:|------|
|
||||
| **통합 반복 데이터** | ❌ 미적용 | ✅ 적용 | `mainTableName`, `foreignKeyColumn` 지원, Combobox UI 적용 |
|
||||
| **반복 화면 모달** | ❌ 미적용 | ⚠️ 부분 | `tableName` 설정 가능하나 Combobox UI 없음 |
|
||||
|
||||
---
|
||||
|
||||
### 액션 (Action) - 1개
|
||||
|
||||
| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 |
|
||||
|---------|:----------:|:----------:|------|
|
||||
| **기본 버튼** | ✅ 적용 | ➖ 해당없음 | `langKeyId`, `langKey` 지원 |
|
||||
|
||||
---
|
||||
|
||||
### 레이아웃 (Layout) - 5개
|
||||
|
||||
| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 |
|
||||
|---------|:----------:|:----------:|------|
|
||||
| **분할 패널** | ✅ 적용 | ⚠️ 부분 | 다국어 지원, 테이블 설정은 하위 패널에서 처리 |
|
||||
| **탭 컴포넌트** | ❌ 미적용 | ➖ 해당없음 | 화면 전환용 컨테이너 |
|
||||
| **Section Card** | ❌ 미적용 | ➖ 해당없음 | 그룹화 컨테이너 |
|
||||
| **Section Paper** | ❌ 미적용 | ➖ 해당없음 | 그룹화 컨테이너 |
|
||||
| **구분선** | ❌ 미적용 | ➖ 해당없음 | 시각적 구분용 |
|
||||
|
||||
---
|
||||
|
||||
### 유틸리티 (Utility) - 4개
|
||||
|
||||
| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 |
|
||||
|---------|:----------:|:----------:|------|
|
||||
| **코드 채번 규칙** | ❌ 미적용 | ➖ 해당없음 | 채번 규칙 관리 전용 |
|
||||
| **렉 구조 설정** | ❌ 미적용 | ➖ 해당없음 | 창고 렉 설정 전용 |
|
||||
| **출발지/도착지 선택** | ❌ 미적용 | ⚠️ 부분 | `customTableName` 지원하나 Combobox UI 없음 |
|
||||
| **검색 필터** | ❌ 미적용 | ⚠️ 부분 | `screenTableName` 자동 감지 |
|
||||
|
||||
---
|
||||
|
||||
## 상세 설명
|
||||
|
||||
### 다국어 지원 (`langKeyId`, `langKey`)
|
||||
|
||||
다국어 지원이란 컴포넌트의 라벨, 플레이스홀더 등 텍스트 속성에 다국어 키를 연결하여 언어별로 다른 텍스트를 표시하는 기능입니다.
|
||||
|
||||
**적용 완료 (3개)**
|
||||
- `table-list`: 컬럼 라벨 다국어 지원
|
||||
- `button-primary`: 버튼 텍스트 다국어 지원
|
||||
- `split-panel-layout`: 패널 제목 다국어 지원
|
||||
|
||||
**미적용 (10개)**
|
||||
- `card-display`, `text-display`, `pivot-grid`
|
||||
- `unified-repeater`, `repeat-screen-modal`
|
||||
- `tabs`, `section-card`, `section-paper`, `divider-line`
|
||||
- `numbering-rule`, `rack-structure`, `location-swap-selector`, `table-search-widget`
|
||||
|
||||
---
|
||||
|
||||
### 컴포넌트별 테이블 설정 (`customTableName`, `useCustomTable`)
|
||||
|
||||
컴포넌트별 테이블 설정이란 화면의 메인 테이블과 별개로 컴포넌트가 자체적으로 사용할 테이블을 지정할 수 있는 기능입니다.
|
||||
|
||||
**완전 적용 (4개)**
|
||||
|
||||
| 컴포넌트 | 적용 방식 |
|
||||
|---------|----------|
|
||||
| `table-list` | Combobox UI로 테이블 선택, `customTableName`, `useCustomTable`, `isReadOnly` 지원 |
|
||||
| `unified-repeater` | Combobox UI로 테이블 선택, `mainTableName`, `foreignKeyColumn` 지원, FK 자동 연결 |
|
||||
| `unified-list` | `TableListConfigPanel` 래핑하여 동일 기능 제공 |
|
||||
|
||||
**부분 적용 (6개)**
|
||||
|
||||
| 컴포넌트 | 현재 상태 | 필요 작업 |
|
||||
|---------|----------|----------|
|
||||
| `card-display` | `screenTableName` 사용 | Combobox UI 추가 필요 |
|
||||
| `pivot-grid` | `tableName` 설정 가능 | Combobox UI 추가 필요 |
|
||||
| `repeat-screen-modal` | `tableName` 설정 가능 | Combobox UI 추가 필요 |
|
||||
| `split-panel-layout` | 하위 패널에서 처리 | 하위 컴포넌트에 위임 |
|
||||
| `location-swap-selector` | `customTableName` 지원 | Combobox UI 추가 필요 |
|
||||
| `table-search-widget` | `screenTableName` 자동 감지 | 현재 방식 유지 가능 |
|
||||
|
||||
**해당없음 (6개)**
|
||||
- `text-display`, `divider-line`: 정적 컴포넌트
|
||||
- `tabs`, `section-card`, `section-paper`: 레이아웃 컨테이너
|
||||
- `numbering-rule`, `rack-structure`: 특수 목적 컴포넌트
|
||||
|
||||
---
|
||||
|
||||
## 우선순위 작업 목록
|
||||
|
||||
### 1순위: 데이터 컴포넌트 테이블 설정 UI 통일
|
||||
|
||||
| 컴포넌트 | 작업 내용 |
|
||||
|---------|----------|
|
||||
| `card-display` | Combobox UI 추가, `customTableName` 지원 |
|
||||
| `pivot-grid` | Combobox UI 추가 |
|
||||
| `repeat-screen-modal` | Combobox UI 추가 |
|
||||
|
||||
### 2순위: 다국어 지원 확대
|
||||
|
||||
| 컴포넌트 | 작업 내용 |
|
||||
|---------|----------|
|
||||
| `unified-repeater` | 컬럼 라벨 `langKeyId` 지원 |
|
||||
| `card-display` | 필드 라벨 `langKeyId` 지원 |
|
||||
| `tabs` | 탭 이름 `langKeyId` 지원 |
|
||||
| `section-card` | 제목 `langKeyId` 지원 |
|
||||
|
||||
---
|
||||
|
||||
## 범례
|
||||
|
||||
| 기호 | 의미 |
|
||||
|-----|------|
|
||||
| ✅ | 완전 적용 |
|
||||
| ⚠️ | 부분 적용 (기능은 있으나 UI 미비) |
|
||||
| ❌ | 미적용 |
|
||||
| ➖ | 해당없음 (기능 불필요) |
|
||||
|
||||
|
|
@ -66,14 +66,7 @@ export function ComponentsPanel({
|
|||
// unified-date: 테이블 컬럼 드래그 시 자동 생성되므로 숨김 처리
|
||||
// unified-layout: 중첩 드래그앤드롭 기능 미구현으로 숨김 처리
|
||||
// unified-group: 중첩 드래그앤드롭 기능 미구현으로 숨김 처리
|
||||
{
|
||||
id: "unified-list",
|
||||
name: "통합 목록",
|
||||
description: "테이블, 카드 등 다양한 데이터 표시 방식 지원",
|
||||
category: "display" as ComponentCategory,
|
||||
tags: ["table", "list", "card", "unified"],
|
||||
defaultSize: { width: 600, height: 400 },
|
||||
},
|
||||
// unified-list: table-list, card-display로 분리하여 숨김 처리
|
||||
// unified-media 제거 - 테이블 컬럼의 image/file 입력 타입으로 사용
|
||||
// unified-biz 제거 - 개별 컴포넌트(flow-widget, rack-structure, numbering-rule)로 직접 표시
|
||||
// unified-hierarchy 제거 - 현재 미사용
|
||||
|
|
@ -113,8 +106,7 @@ export function ComponentsPanel({
|
|||
// 특수 업무용 컴포넌트 (일반 화면에서 불필요)
|
||||
"tax-invoice-list", // 세금계산서 전용
|
||||
"customer-item-mapping", // 고객-품목 매핑 전용
|
||||
// unified-list로 통합됨
|
||||
"card-display", // → unified-list (card 모드)
|
||||
// card-display는 별도 컴포넌트로 유지
|
||||
// unified-media로 통합됨
|
||||
"image-display", // → unified-media (image)
|
||||
// 공통코드관리로 통합 예정
|
||||
|
|
@ -128,6 +120,12 @@ export function ComponentsPanel({
|
|||
"universal-form-modal", // 범용 폼 모달
|
||||
// 통합 미디어 (테이블 컬럼 입력 타입으로 사용)
|
||||
"unified-media", // → 테이블 컬럼의 image/file 입력 타입으로 사용
|
||||
// 플로우 위젯 숨김 처리
|
||||
"flow-widget",
|
||||
// 선택 항목 상세입력 - 기존 컴포넌트 조합으로 대체 가능
|
||||
"selected-items-detail-input",
|
||||
// 연관 데이터 버튼 - unified-repeater로 대체 가능
|
||||
"related-data-buttons",
|
||||
];
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -2,21 +2,13 @@
|
|||
|
||||
/**
|
||||
* UnifiedList 설정 패널
|
||||
* 통합 목록 컴포넌트의 세부 설정을 관리합니다.
|
||||
* - 현재 화면의 테이블 데이터를 사용
|
||||
* - 테이블 컬럼 + 엔티티 조인 컬럼 선택 지원
|
||||
* TableListConfigPanel을 래핑하여 동일한 설정 기능을 제공합니다.
|
||||
* 카드 표시는 별도의 card-display 컴포넌트를 사용합니다.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useMemo } from "react";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Database, Link2, GripVertical, ChevronDown, ChevronRight } from "lucide-react";
|
||||
import { tableTypeApi } from "@/lib/api/screen";
|
||||
import { cn } from "@/lib/utils";
|
||||
import React, { useMemo } from "react";
|
||||
import { TableListConfigPanel } from "@/lib/registry/components/table-list/TableListConfigPanel";
|
||||
import { TableListConfig } from "@/lib/registry/components/table-list/types";
|
||||
|
||||
interface UnifiedListConfigPanelProps {
|
||||
config: Record<string, any>;
|
||||
|
|
@ -25,476 +17,148 @@ interface UnifiedListConfigPanelProps {
|
|||
currentTableName?: string;
|
||||
}
|
||||
|
||||
interface ColumnOption {
|
||||
columnName: string;
|
||||
displayName: string;
|
||||
isJoinColumn?: boolean;
|
||||
sourceTable?: string;
|
||||
inputType?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* UnifiedList 설정 패널
|
||||
* TableListConfigPanel과 동일한 기능을 제공
|
||||
*/
|
||||
export const UnifiedListConfigPanel: React.FC<UnifiedListConfigPanelProps> = ({
|
||||
config,
|
||||
onChange,
|
||||
currentTableName,
|
||||
}) => {
|
||||
// 컬럼 목록 (테이블 컬럼 + 엔티티 조인 컬럼)
|
||||
const [columns, setColumns] = useState<ColumnOption[]>([]);
|
||||
const [loadingColumns, setLoadingColumns] = useState(false);
|
||||
const [expandedJoinSections, setExpandedJoinSections] = useState<Set<string>>(new Set());
|
||||
// UnifiedList config를 TableListConfig 형식으로 변환
|
||||
const tableListConfig: TableListConfig = useMemo(() => {
|
||||
// 컬럼 형식 변환: UnifiedList columns -> TableList columns
|
||||
const columns = (config.columns || []).map((col: any, index: number) => ({
|
||||
columnName: col.key || col.columnName || col.field || "",
|
||||
displayName: col.title || col.header || col.displayName || col.key || col.columnName || col.field || "",
|
||||
width: col.width ? parseInt(col.width, 10) : undefined,
|
||||
visible: col.visible !== false,
|
||||
sortable: col.sortable !== false,
|
||||
searchable: col.searchable !== false,
|
||||
align: col.align || "left",
|
||||
order: index,
|
||||
isEntityJoin: col.isJoinColumn || col.isEntityJoin || false,
|
||||
thousandSeparator: col.thousandSeparator,
|
||||
editable: col.editable,
|
||||
entityDisplayConfig: col.entityDisplayConfig,
|
||||
}));
|
||||
|
||||
// 설정 업데이트 핸들러
|
||||
const updateConfig = (field: string, value: any) => {
|
||||
const newConfig = { ...config, [field]: value };
|
||||
console.log("⚙️ UnifiedListConfigPanel updateConfig:", { field, value, newConfig });
|
||||
return {
|
||||
selectedTable: config.tableName || config.dataSource?.table || currentTableName,
|
||||
tableName: config.tableName || config.dataSource?.table || currentTableName,
|
||||
columns,
|
||||
useCustomTable: config.useCustomTable,
|
||||
customTableName: config.customTableName,
|
||||
isReadOnly: config.isReadOnly !== false, // UnifiedList는 기본적으로 읽기 전용
|
||||
displayMode: "table", // 테이블 모드 고정 (카드는 card-display 컴포넌트 사용)
|
||||
pagination: config.pagination !== false ? {
|
||||
enabled: true,
|
||||
pageSize: config.pageSize || 10,
|
||||
position: "bottom",
|
||||
showPageSize: true,
|
||||
pageSizeOptions: [5, 10, 20, 50, 100],
|
||||
} : {
|
||||
enabled: false,
|
||||
pageSize: 10,
|
||||
position: "bottom",
|
||||
showPageSize: false,
|
||||
pageSizeOptions: [10],
|
||||
},
|
||||
filter: config.filter,
|
||||
dataFilter: config.dataFilter,
|
||||
checkbox: {
|
||||
enabled: true,
|
||||
position: "left",
|
||||
showHeader: true,
|
||||
},
|
||||
height: "auto",
|
||||
autoWidth: true,
|
||||
stickyHeader: true,
|
||||
autoLoad: true,
|
||||
horizontalScroll: {
|
||||
enabled: true,
|
||||
minColumnWidth: 100,
|
||||
maxColumnWidth: 300,
|
||||
},
|
||||
};
|
||||
}, [config, currentTableName]);
|
||||
|
||||
// TableListConfig 변경을 UnifiedList config 형식으로 변환
|
||||
const handleConfigChange = (partialConfig: Partial<TableListConfig>) => {
|
||||
const newConfig: Record<string, any> = { ...config };
|
||||
|
||||
// 테이블 설정 변환
|
||||
if (partialConfig.selectedTable !== undefined) {
|
||||
newConfig.tableName = partialConfig.selectedTable;
|
||||
if (!newConfig.dataSource) {
|
||||
newConfig.dataSource = {};
|
||||
}
|
||||
newConfig.dataSource.table = partialConfig.selectedTable;
|
||||
}
|
||||
if (partialConfig.tableName !== undefined) {
|
||||
newConfig.tableName = partialConfig.tableName;
|
||||
if (!newConfig.dataSource) {
|
||||
newConfig.dataSource = {};
|
||||
}
|
||||
newConfig.dataSource.table = partialConfig.tableName;
|
||||
}
|
||||
if (partialConfig.useCustomTable !== undefined) {
|
||||
newConfig.useCustomTable = partialConfig.useCustomTable;
|
||||
}
|
||||
if (partialConfig.customTableName !== undefined) {
|
||||
newConfig.customTableName = partialConfig.customTableName;
|
||||
}
|
||||
if (partialConfig.isReadOnly !== undefined) {
|
||||
newConfig.isReadOnly = partialConfig.isReadOnly;
|
||||
}
|
||||
|
||||
// 컬럼 형식 변환: TableList columns -> UnifiedList columns
|
||||
if (partialConfig.columns !== undefined) {
|
||||
newConfig.columns = partialConfig.columns.map((col: any) => ({
|
||||
key: col.columnName,
|
||||
field: col.columnName,
|
||||
title: col.displayName,
|
||||
header: col.displayName,
|
||||
width: col.width ? String(col.width) : undefined,
|
||||
visible: col.visible,
|
||||
sortable: col.sortable,
|
||||
searchable: col.searchable,
|
||||
align: col.align,
|
||||
isJoinColumn: col.isEntityJoin,
|
||||
isEntityJoin: col.isEntityJoin,
|
||||
thousandSeparator: col.thousandSeparator,
|
||||
editable: col.editable,
|
||||
entityDisplayConfig: col.entityDisplayConfig,
|
||||
}));
|
||||
}
|
||||
|
||||
// 페이지네이션 변환
|
||||
if (partialConfig.pagination !== undefined) {
|
||||
newConfig.pagination = partialConfig.pagination?.enabled;
|
||||
newConfig.pageSize = partialConfig.pagination?.pageSize || 10;
|
||||
}
|
||||
|
||||
// 필터 변환
|
||||
if (partialConfig.filter !== undefined) {
|
||||
newConfig.filter = partialConfig.filter;
|
||||
}
|
||||
|
||||
// 데이터 필터 변환
|
||||
if (partialConfig.dataFilter !== undefined) {
|
||||
newConfig.dataFilter = partialConfig.dataFilter;
|
||||
}
|
||||
|
||||
console.log("⚙️ UnifiedListConfigPanel handleConfigChange:", { partialConfig, newConfig });
|
||||
onChange(newConfig);
|
||||
};
|
||||
|
||||
// 테이블명 (현재 화면의 테이블 사용)
|
||||
const tableName = currentTableName || config.tableName;
|
||||
|
||||
// 화면의 테이블명을 config에 자동 저장
|
||||
useEffect(() => {
|
||||
if (currentTableName && config.tableName !== currentTableName) {
|
||||
onChange({ ...config, tableName: currentTableName });
|
||||
}
|
||||
}, [currentTableName]);
|
||||
|
||||
// 테이블 컬럼 및 엔티티 조인 컬럼 로드
|
||||
useEffect(() => {
|
||||
const loadColumns = async () => {
|
||||
if (!tableName) {
|
||||
setColumns([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoadingColumns(true);
|
||||
try {
|
||||
// 1. 테이블 컬럼 로드
|
||||
const columnData = await tableTypeApi.getColumns(tableName);
|
||||
const baseColumns: ColumnOption[] = columnData.map((c: any) => ({
|
||||
columnName: c.columnName || c.column_name,
|
||||
displayName: c.displayName || c.columnLabel || c.columnName || c.column_name,
|
||||
isJoinColumn: false,
|
||||
inputType: c.inputType || c.input_type || c.webType || c.web_type,
|
||||
}));
|
||||
|
||||
// 2. 엔티티 타입 컬럼 찾기 및 조인 컬럼 정보 로드
|
||||
const entityColumns = columnData.filter((c: any) => (c.inputType || c.input_type) === "entity");
|
||||
|
||||
const joinColumnOptions: ColumnOption[] = [];
|
||||
|
||||
for (const entityCol of entityColumns) {
|
||||
const colName = entityCol.columnName || entityCol.column_name;
|
||||
|
||||
// referenceTable 우선순위:
|
||||
// 1. 컬럼의 reference_table 필드
|
||||
// 2. detailSettings.referenceTable
|
||||
let referenceTable = entityCol.referenceTable || entityCol.reference_table;
|
||||
|
||||
if (!referenceTable) {
|
||||
let detailSettings = entityCol.detailSettings || entityCol.detail_settings;
|
||||
if (typeof detailSettings === "string") {
|
||||
try {
|
||||
detailSettings = JSON.parse(detailSettings);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
referenceTable = detailSettings?.referenceTable;
|
||||
}
|
||||
|
||||
if (referenceTable) {
|
||||
try {
|
||||
const refColumnData = await tableTypeApi.getColumns(referenceTable);
|
||||
|
||||
refColumnData.forEach((refCol: any) => {
|
||||
const refColName = refCol.columnName || refCol.column_name;
|
||||
const refDisplayName = refCol.displayName || refCol.columnLabel || refColName;
|
||||
|
||||
joinColumnOptions.push({
|
||||
columnName: `${colName}.${refColName}`,
|
||||
displayName: refDisplayName,
|
||||
isJoinColumn: true,
|
||||
sourceTable: referenceTable,
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`참조 테이블 ${referenceTable} 컬럼 로드 실패:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setColumns([...baseColumns, ...joinColumnOptions]);
|
||||
} catch (error) {
|
||||
console.error("컬럼 목록 로드 실패:", error);
|
||||
setColumns([]);
|
||||
} finally {
|
||||
setLoadingColumns(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadColumns();
|
||||
}, [tableName]);
|
||||
|
||||
// 컬럼 설정
|
||||
const configColumns: Array<{
|
||||
key: string;
|
||||
title: string;
|
||||
width?: string;
|
||||
isJoinColumn?: boolean;
|
||||
inputType?: string;
|
||||
thousandSeparator?: boolean;
|
||||
}> = config.columns || [];
|
||||
|
||||
// 컬럼이 추가되었는지 확인
|
||||
const isColumnAdded = (columnName: string) => {
|
||||
return configColumns.some((col) => col.key === columnName);
|
||||
};
|
||||
|
||||
// 컬럼 토글 (추가/제거)
|
||||
const toggleColumn = (column: ColumnOption) => {
|
||||
if (isColumnAdded(column.columnName)) {
|
||||
// 제거
|
||||
const newColumns = configColumns.filter((col) => col.key !== column.columnName);
|
||||
updateConfig("columns", newColumns);
|
||||
} else {
|
||||
// 추가
|
||||
const isNumberType = ["number", "decimal", "integer", "float", "double", "numeric", "currency"].includes(
|
||||
column.inputType || "",
|
||||
);
|
||||
const newColumn = {
|
||||
key: column.columnName,
|
||||
title: column.displayName,
|
||||
width: "",
|
||||
isJoinColumn: column.isJoinColumn || false,
|
||||
inputType: column.inputType,
|
||||
thousandSeparator: isNumberType ? true : undefined, // 숫자 타입은 기본적으로 천단위 구분자 사용
|
||||
};
|
||||
updateConfig("columns", [...configColumns, newColumn]);
|
||||
}
|
||||
};
|
||||
|
||||
// 컬럼 천단위 구분자 토글
|
||||
const toggleThousandSeparator = (columnKey: string, checked: boolean) => {
|
||||
const newColumns = configColumns.map((col) => (col.key === columnKey ? { ...col, thousandSeparator: checked } : col));
|
||||
updateConfig("columns", newColumns);
|
||||
};
|
||||
|
||||
// 숫자 타입 컬럼인지 확인
|
||||
const isNumberColumn = (columnKey: string) => {
|
||||
const colInfo = columns.find((c) => c.columnName === columnKey);
|
||||
const configCol = configColumns.find((c) => c.key === columnKey);
|
||||
const inputType = configCol?.inputType || colInfo?.inputType || "";
|
||||
return ["number", "decimal", "integer", "float", "double", "numeric", "currency"].includes(inputType);
|
||||
};
|
||||
|
||||
// 컬럼 제목 수정
|
||||
const updateColumnTitle = (columnKey: string, title: string) => {
|
||||
const newColumns = configColumns.map((col) => (col.key === columnKey ? { ...col, title } : col));
|
||||
updateConfig("columns", newColumns);
|
||||
};
|
||||
|
||||
// 그룹별 컬럼 분리
|
||||
const baseColumns = useMemo(() => columns.filter((col) => !col.isJoinColumn), [columns]);
|
||||
|
||||
// 조인 컬럼을 소스 테이블별로 그룹화
|
||||
const joinColumnsByTable = useMemo(() => {
|
||||
const grouped: Record<string, ColumnOption[]> = {};
|
||||
columns
|
||||
.filter((col) => col.isJoinColumn)
|
||||
.forEach((col) => {
|
||||
const table = col.sourceTable || "unknown";
|
||||
if (!grouped[table]) {
|
||||
grouped[table] = [];
|
||||
}
|
||||
grouped[table].push(col);
|
||||
});
|
||||
return grouped;
|
||||
}, [columns]);
|
||||
|
||||
// 조인 섹션 토글
|
||||
const toggleJoinSection = (tableName: string) => {
|
||||
setExpandedJoinSections((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(tableName)) {
|
||||
newSet.delete(tableName);
|
||||
} else {
|
||||
newSet.add(tableName);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* 뷰 모드 */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs font-medium">표시 방식</Label>
|
||||
<Select value={config.viewMode || "table"} onValueChange={(value) => updateConfig("viewMode", value)}>
|
||||
<SelectTrigger className="h-8 text-xs">
|
||||
<SelectValue placeholder="방식 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="table">테이블</SelectItem>
|
||||
<SelectItem value="card">카드</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 카드 모드 설정 */}
|
||||
{config.viewMode === "card" && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="space-y-3">
|
||||
<Label className="text-xs font-medium">카드 설정</Label>
|
||||
|
||||
{/* 제목 컬럼 */}
|
||||
<div className="space-y-1">
|
||||
<Label className="text-muted-foreground text-[10px]">제목 컬럼</Label>
|
||||
<Select
|
||||
value={config.cardConfig?.titleColumn || ""}
|
||||
onValueChange={(value) => updateConfig("cardConfig", { ...config.cardConfig, titleColumn: value })}
|
||||
>
|
||||
<SelectTrigger className="h-7 text-xs">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{configColumns.map((col: any) => (
|
||||
<SelectItem key={col.key} value={col.key}>
|
||||
{col.title}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 부제목 컬럼 */}
|
||||
<div className="space-y-1">
|
||||
<Label className="text-muted-foreground text-[10px]">부제목 컬럼</Label>
|
||||
<Select
|
||||
value={config.cardConfig?.subtitleColumn || "_none_"}
|
||||
onValueChange={(value) =>
|
||||
updateConfig("cardConfig", { ...config.cardConfig, subtitleColumn: value === "_none_" ? "" : value })
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="h-7 text-xs">
|
||||
<SelectValue placeholder="선택 (선택사항)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="_none_">없음</SelectItem>
|
||||
{configColumns.map((col: any) => (
|
||||
<SelectItem key={col.key} value={col.key}>
|
||||
{col.title}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 행당 카드 수 */}
|
||||
<div className="space-y-1">
|
||||
<Label className="text-muted-foreground text-[10px]">행당 카드 수</Label>
|
||||
<Select
|
||||
value={String(config.cardConfig?.cardsPerRow || 3)}
|
||||
onValueChange={(value) =>
|
||||
updateConfig("cardConfig", { ...config.cardConfig, cardsPerRow: parseInt(value) })
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="h-7 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1">1개</SelectItem>
|
||||
<SelectItem value="2">2개</SelectItem>
|
||||
<SelectItem value="3">3개</SelectItem>
|
||||
<SelectItem value="4">4개</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* 컬럼 선택 */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs font-medium">표시할 컬럼 선택</Label>
|
||||
|
||||
{loadingColumns ? (
|
||||
<p className="text-muted-foreground py-2 text-xs">컬럼 로딩 중...</p>
|
||||
) : !tableName ? (
|
||||
<p className="text-muted-foreground py-2 text-xs">테이블을 선택해주세요</p>
|
||||
) : (
|
||||
<div className="max-h-60 space-y-2 overflow-y-auto rounded-md border p-2">
|
||||
{/* 테이블 컬럼 */}
|
||||
<div className="space-y-0.5">
|
||||
{baseColumns.map((column) => (
|
||||
<div
|
||||
key={column.columnName}
|
||||
className={cn(
|
||||
"hover:bg-muted/50 flex cursor-pointer items-center gap-2 rounded px-2 py-1",
|
||||
isColumnAdded(column.columnName) && "bg-primary/10",
|
||||
)}
|
||||
onClick={() => toggleColumn(column)}
|
||||
>
|
||||
<Checkbox
|
||||
checked={isColumnAdded(column.columnName)}
|
||||
onCheckedChange={() => toggleColumn(column)}
|
||||
className="pointer-events-none h-3.5 w-3.5 flex-shrink-0"
|
||||
/>
|
||||
<Database className="text-muted-foreground h-3 w-3 flex-shrink-0" />
|
||||
<span className="truncate text-xs">{column.displayName}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 조인 컬럼 (테이블별 그룹) */}
|
||||
{Object.keys(joinColumnsByTable).length > 0 && (
|
||||
<div className="mt-2 border-t pt-2">
|
||||
<div className="text-muted-foreground mb-1 flex items-center gap-1 text-[10px] font-medium">
|
||||
<Link2 className="h-3 w-3 text-blue-500" />
|
||||
엔티티 조인 컬럼
|
||||
</div>
|
||||
{Object.entries(joinColumnsByTable).map(([refTable, refColumns]) => (
|
||||
<div key={refTable} className="mb-1">
|
||||
<div
|
||||
className="hover:bg-muted/30 flex cursor-pointer items-center gap-1 rounded px-1 py-0.5"
|
||||
onClick={() => toggleJoinSection(refTable)}
|
||||
>
|
||||
{expandedJoinSections.has(refTable) ? (
|
||||
<ChevronDown className="text-muted-foreground h-3 w-3 flex-shrink-0" />
|
||||
) : (
|
||||
<ChevronRight className="text-muted-foreground h-3 w-3 flex-shrink-0" />
|
||||
)}
|
||||
<span className="truncate text-[10px] font-medium text-blue-600">{refTable}</span>
|
||||
<span className="text-muted-foreground text-[10px]">({refColumns.length})</span>
|
||||
</div>
|
||||
|
||||
{expandedJoinSections.has(refTable) && (
|
||||
<div className="ml-3 space-y-0.5">
|
||||
{refColumns.map((column) => (
|
||||
<div
|
||||
key={column.columnName}
|
||||
className={cn(
|
||||
"hover:bg-muted/50 flex cursor-pointer items-center gap-2 rounded px-2 py-1",
|
||||
isColumnAdded(column.columnName) && "bg-blue-50",
|
||||
)}
|
||||
onClick={() => toggleColumn(column)}
|
||||
>
|
||||
<Checkbox
|
||||
checked={isColumnAdded(column.columnName)}
|
||||
onCheckedChange={() => toggleColumn(column)}
|
||||
className="pointer-events-none h-3.5 w-3.5 flex-shrink-0"
|
||||
/>
|
||||
<span className="min-w-0 flex-1 truncate text-xs">{column.displayName}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 선택된 컬럼 상세 설정 */}
|
||||
{configColumns.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs font-medium">선택된 컬럼 ({configColumns.length}개)</Label>
|
||||
<div className="max-h-40 space-y-1 overflow-y-auto">
|
||||
{configColumns.map((column, index) => {
|
||||
const colInfo = columns.find((c) => c.columnName === column.key);
|
||||
const showThousandSeparator = isNumberColumn(column.key);
|
||||
return (
|
||||
<div key={column.key} className="bg-muted/30 space-y-1.5 rounded-md border p-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<GripVertical className="text-muted-foreground h-3 w-3 cursor-grab" />
|
||||
{column.isJoinColumn ? (
|
||||
<Link2 className="h-3 w-3 flex-shrink-0 text-blue-500" />
|
||||
) : (
|
||||
<Database className="text-muted-foreground h-3 w-3 flex-shrink-0" />
|
||||
)}
|
||||
<Input
|
||||
value={column.title}
|
||||
onChange={(e) => updateColumnTitle(column.key, e.target.value)}
|
||||
placeholder="제목"
|
||||
className="h-6 flex-1 text-xs"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => toggleColumn(colInfo || { columnName: column.key, displayName: column.title })}
|
||||
className="text-destructive h-6 w-6 p-0"
|
||||
>
|
||||
×
|
||||
</Button>
|
||||
</div>
|
||||
{/* 숫자 컬럼인 경우 천단위 구분자 옵션 표시 */}
|
||||
{showThousandSeparator && (
|
||||
<div className="ml-5 flex items-center gap-2">
|
||||
<Checkbox
|
||||
id={`thousand-${column.key}`}
|
||||
checked={column.thousandSeparator !== false}
|
||||
onCheckedChange={(checked) => toggleThousandSeparator(column.key, !!checked)}
|
||||
className="h-3 w-3"
|
||||
/>
|
||||
<label htmlFor={`thousand-${column.key}`} className="text-muted-foreground text-[10px]">
|
||||
천단위 구분자
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* 페이지네이션 설정 */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="pagination"
|
||||
checked={config.pagination !== false}
|
||||
onCheckedChange={(checked) => updateConfig("pagination", checked)}
|
||||
/>
|
||||
<label htmlFor="pagination" className="text-xs font-medium">
|
||||
페이지네이션
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{config.pagination !== false && (
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs">페이지당 행 수</Label>
|
||||
<Select
|
||||
value={String(config.pageSize || 10)}
|
||||
onValueChange={(value) => updateConfig("pageSize", Number(value))}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="5">5개</SelectItem>
|
||||
<SelectItem value="10">10개</SelectItem>
|
||||
<SelectItem value="20">20개</SelectItem>
|
||||
<SelectItem value="50">50개</SelectItem>
|
||||
<SelectItem value="100">100개</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<TableListConfigPanel
|
||||
config={tableListConfig}
|
||||
onChange={handleConfigChange}
|
||||
screenTableName={currentTableName}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue