다국어 지원 및 테이블 설정 현황 문서를 업데이트하고, SplitPanelLayoutConfigPanel에서 좌측 패널 테이블 선택 기능을 추가했습니다. 또한, 조인 키를 연결 키로 변경하고, 조건 필터 모드에 대한 설명을 수정하여 사용자 경험을 개선했습니다.

This commit is contained in:
kjs 2026-01-15 17:35:04 +09:00
parent 7181822832
commit ce85528ddf
3 changed files with 154 additions and 55 deletions

View File

@ -10,7 +10,7 @@
| 기능 | 적용 완료 | 미적용 | 해당없음 |
| -------------------------- | --------- | ------ | -------- |
| **다국어 지원** | 3개 | 10개 | 3개 |
| **컴포넌트별 테이블 설정** | 5개 | 5개 | 6개 |
| **컴포넌트별 테이블 설정** | 6개 | 4개 | 6개 |
---
@ -46,13 +46,13 @@
### 레이아웃 (Layout) - 5개
| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 |
| ----------------- | :---------: | :---------: | --------------------------------------------- |
| **분할 패널** | ✅ 적용 | ⚠️ 부분 | 다국어 지원, 테이블 설정은 하위 패널에서 처리 |
| **탭 컴포넌트** | ❌ 미적용 | 해당없음 | 화면 전환용 컨테이너 |
| **Section Card** | ❌ 미적용 | 해당없음 | 그룹화 컨테이너 |
| **Section Paper** | ❌ 미적용 | 해당없음 | 그룹화 컨테이너 |
| **구분선** | ❌ 미적용 | 해당없음 | 시각적 구분용 |
| 컴포넌트 | 다국어 지원 | 테이블 설정 | 비고 |
| ----------------- | :---------: | :---------: | ----------------------------------------------------- |
| **분할 패널** | ✅ 적용 | ✅ 적용 | 다국어 지원, 좌우 패널 각각 Combobox UI로 테이블 선택 |
| **탭 컴포넌트** | ❌ 미적용 | 해당없음 | 화면 전환용 컨테이너 |
| **Section Card** | ❌ 미적용 | 해당없음 | 그룹화 컨테이너 |
| **Section Paper** | ❌ 미적용 | 해당없음 | 그룹화 컨테이너 |
| **구분선** | ❌ 미적용 | 해당없음 | 시각적 구분용 |
---
@ -94,20 +94,20 @@
**완전 적용 (5개)**
| 컴포넌트 | 적용 방식 |
| ------------------ | --------------------------------------------------------------------------------- |
| `table-list` | Combobox UI로 테이블 선택, `customTableName`, `useCustomTable`, `isReadOnly` 지원 |
| `unified-repeater` | Combobox UI로 테이블 선택, `mainTableName`, `foreignKeyColumn` 지원, FK 자동 연결 |
| `unified-list` | `TableListConfigPanel` 래핑하여 동일 기능 제공 |
| `card-display` | Combobox UI로 테이블 선택, `customTableName`, `useCustomTable` 지원 |
| 컴포넌트 | 적용 방식 |
| -------------------- | --------------------------------------------------------------------------------- |
| `table-list` | Combobox UI로 테이블 선택, `customTableName`, `useCustomTable`, `isReadOnly` 지원 |
| `unified-repeater` | Combobox UI로 테이블 선택, `mainTableName`, `foreignKeyColumn` 지원, FK 자동 연결 |
| `unified-list` | `TableListConfigPanel` 래핑하여 동일 기능 제공 |
| `card-display` | Combobox UI로 테이블 선택, `customTableName`, `useCustomTable` 지원 |
| `split-panel-layout` | 좌우 패널 각각 Combobox UI로 테이블 선택, 다국어 지원 |
**부분 적용 (5개)**
**부분 적용 (4개)**
| 컴포넌트 | 현재 상태 | 필요 작업 |
| ------------------------ | --------------------------- | --------------------- |
| `pivot-grid` | `tableName` 설정 가능 | Combobox UI 추가 필요 |
| `repeat-screen-modal` | `tableName` 설정 가능 | Combobox UI 추가 필요 |
| `split-panel-layout` | 하위 패널에서 처리 | 하위 컴포넌트에 위임 |
| `location-swap-selector` | `customTableName` 지원 | Combobox UI 추가 필요 |
| `table-search-widget` | `screenTableName` 자동 감지 | 현재 방식 유지 가능 |

View File

@ -388,9 +388,9 @@ const AdditionalTabConfigPanel: React.FC<AdditionalTabConfigPanelProps> = ({
)}
</div>
{/* ===== 4. 컬럼 매핑 (조인 키) ===== */}
{/* ===== 4. 컬럼 매핑 (연결 키) ===== */}
<div className="space-y-3 rounded-lg border bg-white p-3">
<Label className="text-xs font-semibold text-blue-600"> ( )</Label>
<Label className="text-xs font-semibold text-blue-600"> ( )</Label>
<p className="text-[10px] text-gray-500">
</p>
@ -1067,10 +1067,11 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
screenTableName, // 현재 화면의 테이블명
menuObjid, // 🆕 메뉴 OBJID
}) => {
const [leftTableOpen, setLeftTableOpen] = useState(false); // 🆕 좌측 테이블 Combobox 상태
const [rightTableOpen, setRightTableOpen] = useState(false);
const [loadedTableColumns, setLoadedTableColumns] = useState<Record<string, ColumnInfo[]>>({});
const [loadingColumns, setLoadingColumns] = useState<Record<string, boolean>>({});
const [allTables, setAllTables] = useState<any[]>([]); // 조인 모드용 전체 테이블 목록
const [allTables, setAllTables] = useState<any[]>([]); // 테이블 목록
// 엔티티 참조 테이블 컬럼
type EntityRefTable = { tableName: string; columns: ColumnInfo[] };
const [entityReferenceTables, setEntityReferenceTables] = useState<Record<string, EntityRefTable[]>>({});
@ -1095,35 +1096,27 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
}
}, [config.leftPanel?.title, config.rightPanel?.title, isUserEditing]);
// 조인 모드일 때만 전체 테이블 목록 로드
// 전체 테이블 목록 항상 로드 (좌측/우측 모두 사용)
useEffect(() => {
if (relationshipType === "join") {
const loadAllTables = async () => {
try {
const { tableManagementApi } = await import("@/lib/api/tableManagement");
const response = await tableManagementApi.getTableList();
if (response.success && response.data) {
console.log("✅ 분할패널 조인 모드: 전체 테이블 목록 로드", response.data.length, "개");
setAllTables(response.data);
}
} catch (error) {
console.error("❌ 전체 테이블 목록 로드 실패:", error);
const loadAllTables = async () => {
try {
const { tableManagementApi } = await import("@/lib/api/tableManagement");
const response = await tableManagementApi.getTableList();
if (response.success && response.data) {
console.log("✅ 분할패널: 전체 테이블 목록 로드", response.data.length, "개");
setAllTables(response.data);
}
};
loadAllTables();
} else {
// 상세 모드일 때는 기본 테이블만 사용
setAllTables([]);
}
}, [relationshipType]);
// screenTableName이 변경되면 좌측 패널 테이블을 항상 화면 테이블로 설정
useEffect(() => {
if (screenTableName) {
// 좌측 패널은 항상 현재 화면의 테이블 사용
if (config.leftPanel?.tableName !== screenTableName) {
updateLeftPanel({ tableName: screenTableName });
} catch (error) {
console.error("❌ 전체 테이블 목록 로드 실패:", error);
}
};
loadAllTables();
}, []);
// 초기 로드 시 좌측 패널 테이블이 없으면 화면 테이블로 설정
useEffect(() => {
if (screenTableName && !config.leftPanel?.tableName) {
updateLeftPanel({ tableName: screenTableName });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [screenTableName]);
@ -1345,8 +1338,13 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
return leftTableName ? loadedTableColumns[leftTableName] || [] : [];
}, [loadedTableColumns, leftTableName]);
// 우측 테이블명
const rightTableName = config.rightPanel?.tableName || "";
// 우측 테이블명 (상세 모드에서는 좌측과 동일)
const rightTableName = useMemo(() => {
if (relationshipType === "detail") {
return leftTableName; // 상세 모드에서는 좌측과 동일
}
return config.rightPanel?.tableName || "";
}, [relationshipType, leftTableName, config.rightPanel?.tableName]);
// 우측 테이블 컬럼 (로드된 컬럼 사용)
const rightTableColumns = useMemo(() => {
@ -1406,8 +1404,8 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
</SelectItem>
<SelectItem value="join">
<div className="flex flex-col">
<span className="font-medium">(JOIN)</span>
<span className="text-xs text-gray-500"> ( )</span>
<span className="font-medium"> (FILTERED)</span>
<span className="text-xs text-gray-500"> </span>
</div>
</SelectItem>
</SelectContent>
@ -1418,6 +1416,103 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
<div className="mt-4 space-y-4 border-t pt-4">
<h3 className="text-sm font-semibold"> ()</h3>
{/* 🆕 좌측 패널 테이블 선택 */}
<div className="space-y-2">
<Label className="text-xs font-medium"> </Label>
<Popover open={leftTableOpen} onOpenChange={setLeftTableOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={leftTableOpen}
className="h-9 w-full justify-between text-xs"
>
<div className="flex items-center gap-2 truncate">
<Database className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
<span className="truncate">
{config.leftPanel?.tableName
? allTables.find((t) => (t.tableName || t.table_name) === config.leftPanel?.tableName)?.tableLabel ||
allTables.find((t) => (t.tableName || t.table_name) === config.leftPanel?.tableName)?.displayName ||
config.leftPanel?.tableName
: "테이블을 선택하세요"}
</span>
</div>
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[300px] p-0" align="start">
<Command>
<CommandInput placeholder="테이블 검색..." className="text-xs" />
<CommandList>
<CommandEmpty className="py-3 text-center text-xs text-muted-foreground">
.
</CommandEmpty>
{/* 화면 기본 테이블 */}
{screenTableName && (
<CommandGroup heading="기본 (화면 테이블)">
<CommandItem
value={screenTableName}
onSelect={() => {
updateLeftPanel({ tableName: screenTableName, columns: [] });
setLeftTableOpen(false);
}}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-4 w-4",
config.leftPanel?.tableName === screenTableName ? "opacity-100" : "opacity-0"
)}
/>
<Database className="mr-2 h-3.5 w-3.5 text-blue-500" />
{allTables.find((t) => (t.tableName || t.table_name) === screenTableName)?.tableLabel ||
allTables.find((t) => (t.tableName || t.table_name) === screenTableName)?.displayName ||
screenTableName}
</CommandItem>
</CommandGroup>
)}
{/* 전체 테이블 */}
<CommandGroup heading="전체 테이블">
{allTables
.filter((t) => (t.tableName || t.table_name) !== screenTableName)
.map((table) => {
const tableName = table.tableName || table.table_name;
const displayName = table.tableLabel || table.displayName || tableName;
return (
<CommandItem
key={tableName}
value={tableName}
onSelect={() => {
updateLeftPanel({ tableName, columns: [] });
setLeftTableOpen(false);
}}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-4 w-4",
config.leftPanel?.tableName === tableName ? "opacity-100" : "opacity-0"
)}
/>
<Database className="mr-2 h-3.5 w-3.5 text-muted-foreground" />
<span className="truncate">{displayName}</span>
</CommandItem>
);
})}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
{config.leftPanel?.tableName && config.leftPanel?.tableName !== screenTableName && (
<p className="text-[10px] text-muted-foreground">
.
</p>
)}
</div>
<div className="space-y-2">
<Label> </Label>
<Input
@ -1661,7 +1756,7 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
{/* 우측 패널 설정 */}
<div className="mt-4 space-y-4 border-t pt-4">
<h3 className="text-sm font-semibold"> ({relationshipType === "detail" ? "상세" : "조"})</h3>
<h3 className="text-sm font-semibold"> ({relationshipType === "detail" ? "상세" : "조건 필터"})</h3>
<div className="space-y-2">
<Label> </Label>
@ -1706,9 +1801,9 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
</div>
</div>
) : (
// 조 모드: 전체 테이블에서 선택 가능
// 조건 필터 모드: 전체 테이블에서 선택 가능
<div className="space-y-2">
<Label> ( )</Label>
<Label> </Label>
<Popover open={rightTableOpen} onOpenChange={setRightTableOpen}>
<PopoverTrigger asChild>
<Button
@ -1814,12 +1909,12 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
</div>
)}
{/* 엔티티 설정 선택 - 조 모드에서만 표시 */}
{/* 엔티티 설정 선택 - 조건 필터 모드에서만 표시 */}
{relationshipType !== "detail" && (
<div className="space-y-2">
<Label className="text-xs font-medium"> ( )</Label>
<Label className="text-xs font-medium"> </Label>
<p className="text-muted-foreground text-[10px]">
</p>
<Select
value={config.rightPanel?.relation?.foreignKey || ""}

View File

@ -115,6 +115,8 @@ export interface SplitPanelLayoutConfig {
langKey?: string; // 다국어 키
panelHeaderHeight?: number; // 패널 상단 헤더 높이 (px)
tableName?: string; // 데이터베이스 테이블명
useCustomTable?: boolean; // 화면 기본 테이블이 아닌 다른 테이블 사용 여부
customTableName?: string; // 사용자 지정 테이블명 (useCustomTable이 true일 때)
dataSource?: string; // API 엔드포인트
displayMode?: "list" | "table"; // 표시 모드: 목록 또는 테이블
showSearch?: boolean;
@ -180,6 +182,8 @@ export interface SplitPanelLayoutConfig {
langKey?: string; // 다국어 키
panelHeaderHeight?: number; // 패널 상단 헤더 높이 (px)
tableName?: string;
useCustomTable?: boolean; // 화면 기본 테이블이 아닌 다른 테이블 사용 여부
customTableName?: string; // 사용자 지정 테이블명 (useCustomTable이 true일 때)
dataSource?: string;
displayMode?: "list" | "table"; // 표시 모드: 목록 또는 테이블
showSearch?: boolean;