feature/v2-unified-renewal #379
|
|
@ -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` 자동 감지 | 현재 방식 유지 가능 |
|
||||
|
||||
|
|
|
|||
|
|
@ -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 || ""}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue