feat: 분할 패널 레이아웃 멀티테넌시 및 자동 필터링 기능 추가
- 데이터 조회 API에 회사별 자동 필터링 기능 추가 - GET /api/data/:tableName에 company_code 필터 자동 적용 - GET /api/data/join에 우측 테이블 회사별 필터링 추가 - 최고 관리자(company_code = '*')는 전체 데이터 조회 가능 - 분할 패널 레이아웃 우측 추가 시 조인 컬럼 자동 입력 - 좌측에서 선택한 항목의 조인 키 값을 우측 추가 모달에 자동 설정 - 자동 설정된 필드는 읽기 전용으로 표시 (disabled + 안내 문구) - 사용자는 나머지 필드만 입력하면 됨 - 데이터 서비스 개선 - getJoinedData 함수에 companyCode 파라미터 추가 - checkColumnExists 함수를 public으로 변경하여 재사용성 향상 - 조인 쿼리에 DISTINCT 추가로 중복 데이터 방지 - 복합키 테이블의 레코드 삭제 지원 - 레코드 생성 시 멀티테넌시 자동 처리 - company_code와 company_name 자동 추가 - 테이블 컬럼 존재 여부 체크 후 자동 설정 - 분할 패널 설정 UI 개선 - 좌측 패널 표시 컬럼 선택 UI 추가 - 추가 폼에 표시할 컬럼 선택 기능 추가 - Primary Key 정보 자동 조회 및 표시
This commit is contained in:
parent
68577a09f9
commit
68c3db5213
|
|
@ -160,6 +160,16 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
// searchTerm 제거 - 클라이언트 사이드에서 필터링
|
// searchTerm 제거 - 클라이언트 사이드에서 필터링
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 가나다순 정렬 (좌측 패널의 표시 컬럼 기준)
|
||||||
|
const leftColumn = componentConfig.rightPanel?.relation?.leftColumn;
|
||||||
|
if (leftColumn && result.data.length > 0) {
|
||||||
|
result.data.sort((a, b) => {
|
||||||
|
const aValue = String(a[leftColumn] || '');
|
||||||
|
const bValue = String(b[leftColumn] || '');
|
||||||
|
return aValue.localeCompare(bValue, 'ko-KR');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 계층 구조 빌드
|
// 계층 구조 빌드
|
||||||
const hierarchicalData = buildHierarchy(result.data);
|
const hierarchicalData = buildHierarchy(result.data);
|
||||||
setLeftData(hierarchicalData);
|
setLeftData(hierarchicalData);
|
||||||
|
|
@ -173,7 +183,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoadingLeft(false);
|
setIsLoadingLeft(false);
|
||||||
}
|
}
|
||||||
}, [componentConfig.leftPanel?.tableName, isDesignMode, toast, buildHierarchy]);
|
}, [componentConfig.leftPanel?.tableName, componentConfig.rightPanel?.relation?.leftColumn, isDesignMode, toast, buildHierarchy]);
|
||||||
|
|
||||||
// 우측 데이터 로드
|
// 우측 데이터 로드
|
||||||
const loadRightData = useCallback(
|
const loadRightData = useCallback(
|
||||||
|
|
@ -293,9 +303,19 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
// 추가 버튼 핸들러
|
// 추가 버튼 핸들러
|
||||||
const handleAddClick = useCallback((panel: "left" | "right") => {
|
const handleAddClick = useCallback((panel: "left" | "right") => {
|
||||||
setAddModalPanel(panel);
|
setAddModalPanel(panel);
|
||||||
setAddModalFormData({});
|
|
||||||
|
// 우측 패널 추가 시, 좌측에서 선택된 항목의 조인 컬럼 값을 자동으로 채움
|
||||||
|
if (panel === "right" && selectedLeftItem && componentConfig.leftPanel?.leftColumn && componentConfig.rightPanel?.rightColumn) {
|
||||||
|
const leftColumnValue = selectedLeftItem[componentConfig.leftPanel.leftColumn];
|
||||||
|
setAddModalFormData({
|
||||||
|
[componentConfig.rightPanel.rightColumn]: leftColumnValue
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setAddModalFormData({});
|
||||||
|
}
|
||||||
|
|
||||||
setShowAddModal(true);
|
setShowAddModal(true);
|
||||||
}, []);
|
}, [selectedLeftItem, componentConfig]);
|
||||||
|
|
||||||
// 수정 버튼 핸들러
|
// 수정 버튼 핸들러
|
||||||
const handleEditClick = useCallback((panel: "left" | "right", item: any) => {
|
const handleEditClick = useCallback((panel: "left" | "right", item: any) => {
|
||||||
|
|
@ -1316,10 +1336,17 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
|
|
||||||
return modalColumns?.map((col, index) => {
|
return modalColumns?.map((col, index) => {
|
||||||
// 항목별 추가 버튼으로 열렸을 때, parentColumn은 미리 채워져 있고 수정 불가
|
// 항목별 추가 버튼으로 열렸을 때, parentColumn은 미리 채워져 있고 수정 불가
|
||||||
const isPreFilled = addModalPanel === "left-item"
|
const isItemAddPreFilled = addModalPanel === "left-item"
|
||||||
&& componentConfig.leftPanel?.itemAddConfig?.parentColumn === col.name
|
&& componentConfig.leftPanel?.itemAddConfig?.parentColumn === col.name
|
||||||
&& addModalFormData[col.name];
|
&& addModalFormData[col.name];
|
||||||
|
|
||||||
|
// 우측 패널 추가 시, 조인 컬럼(rightColumn)은 미리 채워져 있고 수정 불가
|
||||||
|
const isRightJoinPreFilled = addModalPanel === "right"
|
||||||
|
&& componentConfig.rightPanel?.rightColumn === col.name
|
||||||
|
&& addModalFormData[col.name];
|
||||||
|
|
||||||
|
const isPreFilled = isItemAddPreFilled || isRightJoinPreFilled;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={index}>
|
<div key={index}>
|
||||||
<Label htmlFor={col.name} className="text-xs sm:text-sm">
|
<Label htmlFor={col.name} className="text-xs sm:text-sm">
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { Slider } from "@/components/ui/slider";
|
||||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from "@/components/ui/command";
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from "@/components/ui/command";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
|
||||||
import { Check, ChevronsUpDown, ArrowRight, Plus, X } from "lucide-react";
|
import { Check, ChevronsUpDown, ArrowRight, Plus, X } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { SplitPanelLayoutConfig } from "./types";
|
import { SplitPanelLayoutConfig } from "./types";
|
||||||
|
|
@ -284,7 +285,7 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
|
||||||
console.log(" - availableRightTables:", availableRightTables.length, "개");
|
console.log(" - availableRightTables:", availableRightTables.length, "개");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-4">
|
||||||
{/* 관계 타입 선택 */}
|
{/* 관계 타입 선택 */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h3 className="text-sm font-semibold">패널 관계 타입</h3>
|
<h3 className="text-sm font-semibold">패널 관계 타입</h3>
|
||||||
|
|
@ -324,9 +325,14 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 좌측 패널 설정 (마스터) */}
|
{/* 좌측 패널 설정 (Accordion) */}
|
||||||
<div className="space-y-4">
|
<Accordion type="single" collapsible defaultValue="left-panel" className="w-full">
|
||||||
<h3 className="text-sm font-semibold">좌측 패널 설정 (마스터)</h3>
|
<AccordionItem value="left-panel" className="border rounded-lg px-4">
|
||||||
|
<AccordionTrigger className="text-sm font-semibold hover:no-underline">
|
||||||
|
좌측 패널 설정 (마스터)
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="overflow-visible">
|
||||||
|
<div className="space-y-4 pt-2">
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>패널 제목</Label>
|
<Label>패널 제목</Label>
|
||||||
|
|
@ -807,11 +813,19 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
{/* 우측 패널 설정 */}
|
{/* 우측 패널 설정 (Accordion) */}
|
||||||
<div className="space-y-4">
|
<Accordion type="single" collapsible defaultValue="right-panel" className="w-full">
|
||||||
<h3 className="text-sm font-semibold">우측 패널 설정 ({relationshipType === "detail" ? "상세" : "조인"})</h3>
|
<AccordionItem value="right-panel" className="border rounded-lg px-4">
|
||||||
|
<AccordionTrigger className="text-sm font-semibold hover:no-underline">
|
||||||
|
우측 패널 설정 ({relationshipType === "detail" ? "상세" : "조인"})
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="overflow-visible">
|
||||||
|
<div className="space-y-4 pt-2">
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>패널 제목</Label>
|
<Label>패널 제목</Label>
|
||||||
|
|
@ -1357,11 +1371,19 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
{/* 레이아웃 설정 */}
|
{/* 레이아웃 설정 (Accordion) */}
|
||||||
<div className="space-y-4">
|
<Accordion type="single" collapsible className="w-full">
|
||||||
<h3 className="text-sm font-semibold">레이아웃 설정</h3>
|
<AccordionItem value="layout" className="border rounded-lg px-4">
|
||||||
|
<AccordionTrigger className="text-sm font-semibold hover:no-underline">
|
||||||
|
레이아웃 설정
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="overflow-visible">
|
||||||
|
<div className="space-y-4 pt-2">
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>좌측 패널 너비: {config.splitRatio || 30}%</Label>
|
<Label>좌측 패널 너비: {config.splitRatio || 30}%</Label>
|
||||||
|
|
@ -1389,7 +1411,10 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
|
||||||
onCheckedChange={(checked) => updateConfig({ autoLoad: checked })}
|
onCheckedChange={(checked) => updateConfig({ autoLoad: checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue