분할 패널에서 부서 추가 기능 구현
This commit is contained in:
@@ -6,10 +6,12 @@ import { SplitPanelLayoutConfig } from "./types";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Plus, Search, GripVertical, Loader2, ChevronDown, ChevronUp } from "lucide-react";
|
||||
import { Plus, Search, GripVertical, Loader2, ChevronDown, ChevronUp, Save } from "lucide-react";
|
||||
import { dataApi } from "@/lib/api/data";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { tableTypeApi } from "@/lib/api/screen";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
export interface SplitPanelLayoutComponentProps extends ComponentRendererProps {
|
||||
// 추가 props
|
||||
@@ -47,6 +49,11 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
const [rightTableColumns, setRightTableColumns] = useState<any[]>([]); // 우측 테이블 컬럼 정보
|
||||
const { toast } = useToast();
|
||||
|
||||
// 추가 모달 상태
|
||||
const [showAddModal, setShowAddModal] = useState(false);
|
||||
const [addModalPanel, setAddModalPanel] = useState<"left" | "right" | null>(null);
|
||||
const [addModalFormData, setAddModalFormData] = useState<Record<string, any>>({});
|
||||
|
||||
// 리사이저 드래그 상태
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [leftWidth, setLeftWidth] = useState(splitRatio);
|
||||
@@ -208,6 +215,115 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
loadRightTableColumns();
|
||||
}, [componentConfig.rightPanel?.tableName, isDesignMode]);
|
||||
|
||||
// 추가 버튼 핸들러
|
||||
const handleAddClick = useCallback((panel: "left" | "right") => {
|
||||
setAddModalPanel(panel);
|
||||
setAddModalFormData({});
|
||||
setShowAddModal(true);
|
||||
}, []);
|
||||
|
||||
// 추가 모달 저장
|
||||
const handleAddModalSave = useCallback(async () => {
|
||||
const tableName = addModalPanel === "left"
|
||||
? componentConfig.leftPanel?.tableName
|
||||
: componentConfig.rightPanel?.tableName;
|
||||
|
||||
const modalColumns = addModalPanel === "left"
|
||||
? componentConfig.leftPanel?.addModalColumns
|
||||
: componentConfig.rightPanel?.addModalColumns;
|
||||
|
||||
if (!tableName) {
|
||||
toast({
|
||||
title: "테이블 오류",
|
||||
description: "테이블명이 설정되지 않았습니다.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 필수 필드 검증
|
||||
const requiredFields = (modalColumns || []).filter(col => col.required);
|
||||
for (const field of requiredFields) {
|
||||
if (!addModalFormData[field.name]) {
|
||||
toast({
|
||||
title: "입력 오류",
|
||||
description: `${field.label}은(는) 필수 입력 항목입니다.`,
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("📝 데이터 추가:", { tableName, data: addModalFormData });
|
||||
|
||||
const result = await dataApi.createRecord(tableName, addModalFormData);
|
||||
|
||||
if (result.success) {
|
||||
toast({
|
||||
title: "성공",
|
||||
description: "데이터가 성공적으로 추가되었습니다.",
|
||||
});
|
||||
|
||||
// 모달 닫기
|
||||
setShowAddModal(false);
|
||||
setAddModalFormData({});
|
||||
|
||||
// 데이터 새로고침
|
||||
if (addModalPanel === "left") {
|
||||
loadLeftData();
|
||||
} else if (selectedLeftItem) {
|
||||
loadRightData(selectedLeftItem);
|
||||
}
|
||||
} else {
|
||||
toast({
|
||||
title: "저장 실패",
|
||||
description: result.message || "데이터 추가에 실패했습니다.",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("데이터 추가 오류:", error);
|
||||
|
||||
// 에러 메시지 추출
|
||||
let errorMessage = "데이터 추가 중 오류가 발생했습니다.";
|
||||
|
||||
if (error?.response?.data) {
|
||||
const responseData = error.response.data;
|
||||
|
||||
// 백엔드에서 반환한 에러 메시지 확인
|
||||
if (responseData.error) {
|
||||
// 중복 키 에러 처리
|
||||
if (responseData.error.includes("duplicate key")) {
|
||||
errorMessage = "이미 존재하는 값입니다. 다른 값을 입력해주세요.";
|
||||
}
|
||||
// NOT NULL 제약조건 에러
|
||||
else if (responseData.error.includes("null value")) {
|
||||
const match = responseData.error.match(/column "(\w+)"/);
|
||||
const columnName = match ? match[1] : "필수";
|
||||
errorMessage = `${columnName} 필드는 필수 입력 항목입니다.`;
|
||||
}
|
||||
// 외래키 제약조건 에러
|
||||
else if (responseData.error.includes("foreign key")) {
|
||||
errorMessage = "참조하는 데이터가 존재하지 않습니다.";
|
||||
}
|
||||
// 기타 에러
|
||||
else {
|
||||
errorMessage = responseData.message || responseData.error;
|
||||
}
|
||||
} else if (responseData.message) {
|
||||
errorMessage = responseData.message;
|
||||
}
|
||||
}
|
||||
|
||||
toast({
|
||||
title: "오류",
|
||||
description: errorMessage,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
}, [addModalPanel, componentConfig, addModalFormData, toast, selectedLeftItem, loadLeftData, loadRightData]);
|
||||
|
||||
// 초기 데이터 로드
|
||||
useEffect(() => {
|
||||
if (!isDesignMode && componentConfig.autoLoad !== false) {
|
||||
@@ -295,8 +411,12 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
<CardTitle className="text-base font-semibold">
|
||||
{componentConfig.leftPanel?.title || "좌측 패널"}
|
||||
</CardTitle>
|
||||
{componentConfig.leftPanel?.showAdd && (
|
||||
<Button size="sm" variant="outline">
|
||||
{componentConfig.leftPanel?.showAdd && !isDesignMode && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleAddClick("left")}
|
||||
>
|
||||
<Plus className="mr-1 h-4 w-4" />
|
||||
추가
|
||||
</Button>
|
||||
@@ -478,8 +598,12 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
<CardTitle className="text-base font-semibold">
|
||||
{componentConfig.rightPanel?.title || "우측 패널"}
|
||||
</CardTitle>
|
||||
{componentConfig.rightPanel?.showAdd && (
|
||||
<Button size="sm" variant="outline">
|
||||
{componentConfig.rightPanel?.showAdd && !isDesignMode && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleAddClick("right")}
|
||||
>
|
||||
<Plus className="mr-1 h-4 w-4" />
|
||||
추가
|
||||
</Button>
|
||||
@@ -712,6 +836,63 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 추가 모달 */}
|
||||
<Dialog open={showAddModal} onOpenChange={setShowAddModal}>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base sm:text-lg">
|
||||
{addModalPanel === "left" ? componentConfig.leftPanel?.title : componentConfig.rightPanel?.title} 추가
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-xs sm:text-sm">
|
||||
새로운 데이터를 추가합니다. 필수 항목을 입력해주세요.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-3 sm:space-y-4">
|
||||
{(addModalPanel === "left"
|
||||
? componentConfig.leftPanel?.addModalColumns
|
||||
: componentConfig.rightPanel?.addModalColumns
|
||||
)?.map((col, index) => (
|
||||
<div key={index}>
|
||||
<Label htmlFor={col.name} className="text-xs sm:text-sm">
|
||||
{col.label} {col.required && <span className="text-destructive">*</span>}
|
||||
</Label>
|
||||
<Input
|
||||
id={col.name}
|
||||
value={addModalFormData[col.name] || ""}
|
||||
onChange={(e) => {
|
||||
setAddModalFormData(prev => ({
|
||||
...prev,
|
||||
[col.name]: e.target.value
|
||||
}));
|
||||
}}
|
||||
placeholder={`${col.label} 입력`}
|
||||
className="h-8 text-xs sm:h-10 sm:text-sm"
|
||||
required={col.required}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowAddModal(false)}
|
||||
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleAddModalSave}
|
||||
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
|
||||
>
|
||||
<Save className="mr-1 h-3 w-3 sm:h-4 sm:w-4" />
|
||||
저장
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user