화면 복사기능 구현
This commit is contained in:
192
frontend/components/screen/CopyScreenModal.tsx
Normal file
192
frontend/components/screen/CopyScreenModal.tsx
Normal file
@@ -0,0 +1,192 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Loader2, Copy } from "lucide-react";
|
||||
import { ScreenDefinition } from "@/types/screen";
|
||||
import { screenApi } from "@/lib/api/screen";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface CopyScreenModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
sourceScreen: ScreenDefinition | null;
|
||||
onCopySuccess: () => void;
|
||||
}
|
||||
|
||||
export default function CopyScreenModal({ isOpen, onClose, sourceScreen, onCopySuccess }: CopyScreenModalProps) {
|
||||
const [screenName, setScreenName] = useState("");
|
||||
const [screenCode, setScreenCode] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
|
||||
const [isCopying, setIsCopying] = useState(false);
|
||||
|
||||
// 모달이 열릴 때 초기값 설정
|
||||
useEffect(() => {
|
||||
if (isOpen && sourceScreen) {
|
||||
setScreenName(`${sourceScreen.screenName} (복사본)`);
|
||||
setDescription(sourceScreen.description || "");
|
||||
// 화면 코드 자동 생성
|
||||
generateNewScreenCode();
|
||||
}
|
||||
}, [isOpen, sourceScreen]);
|
||||
|
||||
// 새로운 화면 코드 자동 생성
|
||||
const generateNewScreenCode = async () => {
|
||||
if (!sourceScreen?.companyCode) return;
|
||||
|
||||
try {
|
||||
const newCode = await screenApi.generateScreenCode(sourceScreen.companyCode);
|
||||
setScreenCode(newCode);
|
||||
} catch (error) {
|
||||
console.error("화면 코드 생성 실패:", error);
|
||||
toast.error("화면 코드 생성에 실패했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
// 화면 복사 실행
|
||||
const handleCopy = async () => {
|
||||
if (!sourceScreen) return;
|
||||
|
||||
// 입력값 검증
|
||||
if (!screenName.trim()) {
|
||||
toast.error("화면명을 입력해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!screenCode.trim()) {
|
||||
toast.error("화면 코드 생성에 실패했습니다. 잠시 후 다시 시도해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsCopying(true);
|
||||
|
||||
// 화면 복사 API 호출
|
||||
await screenApi.copyScreen(sourceScreen.screenId, {
|
||||
screenName: screenName.trim(),
|
||||
screenCode: screenCode.trim(),
|
||||
description: description.trim(),
|
||||
});
|
||||
|
||||
toast.success("화면이 성공적으로 복사되었습니다.");
|
||||
onCopySuccess();
|
||||
handleClose();
|
||||
} catch (error: any) {
|
||||
console.error("화면 복사 실패:", error);
|
||||
const errorMessage = error.response?.data?.message || "화면 복사에 실패했습니다.";
|
||||
toast.error(errorMessage);
|
||||
} finally {
|
||||
setIsCopying(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 모달 닫기
|
||||
const handleClose = () => {
|
||||
setScreenName("");
|
||||
setScreenCode("");
|
||||
setDescription("");
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Copy className="h-5 w-5" />
|
||||
화면 복사
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{sourceScreen?.screenName} 화면을 복사합니다. 화면 구성도 함께 복사됩니다.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* 원본 화면 정보 */}
|
||||
<div className="rounded-md bg-gray-50 p-3">
|
||||
<h4 className="mb-2 text-sm font-medium text-gray-700">원본 화면 정보</h4>
|
||||
<div className="space-y-1 text-sm text-gray-600">
|
||||
<div>
|
||||
<span className="font-medium">화면명:</span> {sourceScreen?.screenName}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">화면코드:</span> {sourceScreen?.screenCode}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">회사코드:</span> {sourceScreen?.companyCode}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 새 화면 정보 입력 */}
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label htmlFor="screenName">새 화면명 *</Label>
|
||||
<Input
|
||||
id="screenName"
|
||||
value={screenName}
|
||||
onChange={(e) => setScreenName(e.target.value)}
|
||||
placeholder="복사될 화면의 이름을 입력하세요"
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="screenCode">새 화면코드 (자동생성)</Label>
|
||||
<Input
|
||||
id="screenCode"
|
||||
value={screenCode}
|
||||
readOnly
|
||||
className="mt-1 bg-gray-50"
|
||||
placeholder="화면 코드가 자동으로 생성됩니다"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="description">설명</Label>
|
||||
<Textarea
|
||||
id="description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="화면 설명을 입력하세요 (선택사항)"
|
||||
className="mt-1"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={handleClose} disabled={isCopying}>
|
||||
취소
|
||||
</Button>
|
||||
<Button onClick={handleCopy} disabled={isCopying}>
|
||||
{isCopying ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
복사 중...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="mr-2 h-4 w-4" />
|
||||
복사하기
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import { MoreHorizontal, Edit, Trash2, Copy, Eye, Plus, Search, Palette } from "
|
||||
import { ScreenDefinition } from "@/types/screen";
|
||||
import { screenApi } from "@/lib/api/screen";
|
||||
import CreateScreenModal from "./CreateScreenModal";
|
||||
import CopyScreenModal from "./CopyScreenModal";
|
||||
|
||||
interface ScreenListProps {
|
||||
onScreenSelect: (screen: ScreenDefinition) => void;
|
||||
@@ -30,6 +31,8 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
const [isCreateOpen, setIsCreateOpen] = useState(false);
|
||||
const [isCopyOpen, setIsCopyOpen] = useState(false);
|
||||
const [screenToCopy, setScreenToCopy] = useState<ScreenDefinition | null>(null);
|
||||
|
||||
// 화면 목록 로드 (실제 API)
|
||||
useEffect(() => {
|
||||
@@ -58,6 +61,20 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||
|
||||
const filteredScreens = screens; // 서버 필터 기준 사용
|
||||
|
||||
// 화면 목록 다시 로드
|
||||
const reloadScreens = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const resp = await screenApi.getScreens({ page: currentPage, size: 20, searchTerm });
|
||||
setScreens(resp.data || []);
|
||||
setTotalPages(Math.max(1, Math.ceil((resp.total || 0) / 20)));
|
||||
} catch (e) {
|
||||
console.error("화면 목록 조회 실패", e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleScreenSelect = (screen: ScreenDefinition) => {
|
||||
onScreenSelect(screen);
|
||||
};
|
||||
@@ -75,8 +92,8 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||
};
|
||||
|
||||
const handleCopy = (screen: ScreenDefinition) => {
|
||||
// 복사 모달 열기
|
||||
console.log("복사:", screen);
|
||||
setScreenToCopy(screen);
|
||||
setIsCopyOpen(true);
|
||||
};
|
||||
|
||||
const handleView = (screen: ScreenDefinition) => {
|
||||
@@ -84,6 +101,11 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||
console.log("미리보기:", screen);
|
||||
};
|
||||
|
||||
const handleCopySuccess = () => {
|
||||
// 복사 성공 후 화면 목록 다시 로드
|
||||
reloadScreens();
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
@@ -239,6 +261,14 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||
setScreens((prev) => [created, ...prev]);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 화면 복사 모달 */}
|
||||
<CopyScreenModal
|
||||
isOpen={isCopyOpen}
|
||||
onClose={() => setIsCopyOpen(false)}
|
||||
sourceScreen={screenToCopy}
|
||||
onCopySuccess={handleCopySuccess}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -54,6 +54,8 @@ export const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
||||
canGroup = false,
|
||||
canUngroup = false,
|
||||
}) => {
|
||||
// 데이터테이블 설정 탭 상태를 여기서 관리
|
||||
const [dataTableActiveTab, setDataTableActiveTab] = useState("basic");
|
||||
// 최신 값들의 참조를 유지
|
||||
const selectedComponentRef = useRef(selectedComponent);
|
||||
const onUpdatePropertyRef = useRef(onUpdateProperty);
|
||||
@@ -170,6 +172,8 @@ export const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
||||
key={`datatable-${selectedComponent.id}-${selectedComponent.columns.length}-${selectedComponent.filters.length}-${JSON.stringify(selectedComponent.columns.map((c) => c.id))}-${JSON.stringify(selectedComponent.filters.map((f) => f.columnName))}`}
|
||||
component={selectedComponent as DataTableComponent}
|
||||
tables={tables}
|
||||
activeTab={dataTableActiveTab}
|
||||
onTabChange={setDataTableActiveTab}
|
||||
onUpdateComponent={(updates) => {
|
||||
console.log("🔄 DataTable 컴포넌트 업데이트:", updates);
|
||||
console.log("🔄 업데이트 항목들:", Object.keys(updates));
|
||||
|
||||
Reference in New Issue
Block a user