705 lines
25 KiB
TypeScript
705 lines
25 KiB
TypeScript
"use client";
|
||
|
||
import React, { useState, useEffect } from "react";
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
DialogDescription,
|
||
DialogFooter,
|
||
} 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 {
|
||
Select,
|
||
SelectContent,
|
||
SelectItem,
|
||
SelectTrigger,
|
||
SelectValue,
|
||
} from "@/components/ui/select";
|
||
import { Loader2, Copy, Link as LinkIcon, Trash2, AlertCircle } from "lucide-react";
|
||
import { ScreenDefinition } from "@/types/screen";
|
||
import { screenApi } from "@/lib/api/screen";
|
||
import { apiClient } from "@/lib/api/client";
|
||
import { toast } from "sonner";
|
||
import { useAuth } from "@/hooks/useAuth";
|
||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||
|
||
interface LinkedModalScreen {
|
||
screenId: number;
|
||
screenName: string;
|
||
screenCode: string;
|
||
newScreenName?: string;
|
||
newScreenCode?: string;
|
||
}
|
||
|
||
interface CompanyInfo {
|
||
companyCode: string;
|
||
companyName: string;
|
||
}
|
||
|
||
interface CopyScreenModalProps {
|
||
isOpen: boolean;
|
||
onClose: () => void;
|
||
sourceScreen: ScreenDefinition | null;
|
||
onCopySuccess: () => void;
|
||
}
|
||
|
||
export default function CopyScreenModal({
|
||
isOpen,
|
||
onClose,
|
||
sourceScreen,
|
||
onCopySuccess,
|
||
}: CopyScreenModalProps) {
|
||
const { user } = useAuth();
|
||
// 최고 관리자 판별: userType이 "SUPER_ADMIN" 또는 companyCode가 "*"
|
||
const isSuperAdmin = user?.userType === "SUPER_ADMIN" || user?.companyCode === "*";
|
||
|
||
// 디버깅: 사용자 정보 확인
|
||
useEffect(() => {
|
||
console.log("🔍 CopyScreenModal - User Info:", {
|
||
user,
|
||
isSuperAdmin,
|
||
userType: user?.userType,
|
||
companyCode: user?.companyCode,
|
||
조건1: user?.userType === "SUPER_ADMIN",
|
||
조건2: user?.companyCode === "*",
|
||
최종판별: user?.userType === "SUPER_ADMIN" || user?.companyCode === "*",
|
||
});
|
||
}, [user, isSuperAdmin]);
|
||
|
||
// 메인 화면 복사 정보
|
||
const [screenName, setScreenName] = useState("");
|
||
const [screenCode, setScreenCode] = useState("");
|
||
const [description, setDescription] = useState("");
|
||
|
||
// 대상 회사 선택 (최고 관리자 전용)
|
||
const [targetCompanyCode, setTargetCompanyCode] = useState<string>("");
|
||
const [companies, setCompanies] = useState<CompanyInfo[]>([]);
|
||
const [loadingCompanies, setLoadingCompanies] = useState(false);
|
||
|
||
// 연결된 모달 화면들
|
||
const [linkedScreens, setLinkedScreens] = useState<LinkedModalScreen[]>([]);
|
||
const [loadingLinkedScreens, setLoadingLinkedScreens] = useState(false);
|
||
|
||
// 화면명 일괄 수정 기능
|
||
const [useBulkRename, setUseBulkRename] = useState(false);
|
||
const [removeText, setRemoveText] = useState("");
|
||
const [addPrefix, setAddPrefix] = useState("");
|
||
|
||
// 복사 중 상태
|
||
const [isCopying, setIsCopying] = useState(false);
|
||
|
||
// 최고 관리자인 경우 회사 목록 조회
|
||
useEffect(() => {
|
||
console.log("🔍 회사 목록 조회 체크:", { isSuperAdmin, isOpen });
|
||
if (isSuperAdmin && isOpen) {
|
||
console.log("✅ 회사 목록 조회 시작");
|
||
loadCompanies();
|
||
}
|
||
}, [isSuperAdmin, isOpen]);
|
||
|
||
// 모달이 열릴 때 초기값 설정 및 연결된 화면 감지
|
||
useEffect(() => {
|
||
console.log("🔍 모달 초기화:", { isOpen, sourceScreen, isSuperAdmin });
|
||
if (isOpen && sourceScreen) {
|
||
// 메인 화면 정보 설정
|
||
setScreenName(`${sourceScreen.screenName} (복사본)`);
|
||
setDescription(sourceScreen.description || "");
|
||
|
||
// 대상 회사 코드 설정
|
||
if (isSuperAdmin) {
|
||
setTargetCompanyCode(sourceScreen.companyCode); // 기본값: 원본과 같은 회사
|
||
} else {
|
||
setTargetCompanyCode(sourceScreen.companyCode);
|
||
}
|
||
|
||
// 연결된 모달 화면 감지
|
||
console.log("✅ 연결된 모달 화면 감지 시작");
|
||
detectLinkedModals();
|
||
}
|
||
}, [isOpen, sourceScreen, isSuperAdmin]);
|
||
|
||
// 일괄 변경 설정이 변경될 때 화면명 자동 업데이트
|
||
useEffect(() => {
|
||
if (!sourceScreen) return;
|
||
|
||
if (useBulkRename) {
|
||
// 일괄 수정 사용 시: (복사본) 텍스트 제거
|
||
const newMainName = applyBulkRename(sourceScreen.screenName);
|
||
setScreenName(newMainName);
|
||
|
||
// 모달 화면명 업데이트
|
||
setLinkedScreens((prev) =>
|
||
prev.map((screen) => ({
|
||
...screen,
|
||
newScreenName: applyBulkRename(screen.screenName),
|
||
}))
|
||
);
|
||
} else {
|
||
// 일괄 수정 미사용 시: (복사본) 텍스트 추가
|
||
setScreenName(`${sourceScreen.screenName} (복사본)`);
|
||
|
||
setLinkedScreens((prev) =>
|
||
prev.map((screen) => ({
|
||
...screen,
|
||
newScreenName: screen.screenName,
|
||
}))
|
||
);
|
||
}
|
||
}, [useBulkRename, removeText, addPrefix]);
|
||
|
||
// 대상 회사 변경 시 기존 코드 초기화
|
||
useEffect(() => {
|
||
if (targetCompanyCode) {
|
||
console.log("🔄 회사 변경 → 기존 코드 초기화:", targetCompanyCode);
|
||
setScreenCode("");
|
||
// 모달 화면들의 코드도 초기화
|
||
setLinkedScreens((prev) =>
|
||
prev.map((screen) => ({ ...screen, newScreenCode: undefined }))
|
||
);
|
||
}
|
||
}, [targetCompanyCode]);
|
||
|
||
// linkedScreens 로딩이 완료되면 화면 코드 생성
|
||
useEffect(() => {
|
||
// 모달 화면들의 코드가 모두 설정되었는지 확인
|
||
const allModalCodesSet = linkedScreens.length === 0 ||
|
||
linkedScreens.every(screen => screen.newScreenCode);
|
||
|
||
console.log("🔍 코드 생성 조건 체크:", {
|
||
targetCompanyCode,
|
||
loadingLinkedScreens,
|
||
screenCode,
|
||
linkedScreensCount: linkedScreens.length,
|
||
allModalCodesSet,
|
||
});
|
||
|
||
// 조건: 회사 코드가 있고, 로딩이 완료되고, (메인 코드가 없거나 모달 코드가 없을 때)
|
||
const needsCodeGeneration = targetCompanyCode &&
|
||
!loadingLinkedScreens &&
|
||
(!screenCode || (linkedScreens.length > 0 && !allModalCodesSet));
|
||
|
||
if (needsCodeGeneration) {
|
||
console.log("✅ 화면 코드 생성 시작 (linkedScreens 개수:", linkedScreens.length, ")");
|
||
generateScreenCodes();
|
||
}
|
||
}, [targetCompanyCode, loadingLinkedScreens, screenCode, linkedScreens]);
|
||
|
||
// 회사 목록 조회
|
||
const loadCompanies = async () => {
|
||
try {
|
||
setLoadingCompanies(true);
|
||
const response = await apiClient.get("/admin/companies");
|
||
const data = response.data.data || response.data || [];
|
||
setCompanies(data.map((c: any) => ({
|
||
companyCode: c.company_code || c.companyCode,
|
||
companyName: c.company_name || c.companyName,
|
||
})));
|
||
} catch (error) {
|
||
console.error("회사 목록 조회 실패:", error);
|
||
toast.error("회사 목록을 불러오는데 실패했습니다.");
|
||
} finally {
|
||
setLoadingCompanies(false);
|
||
}
|
||
};
|
||
|
||
// 연결된 모달 화면 감지
|
||
const detectLinkedModals = async () => {
|
||
if (!sourceScreen) return;
|
||
|
||
try {
|
||
setLoadingLinkedScreens(true);
|
||
console.log("📡 API 호출: detectLinkedModals", sourceScreen.screenId);
|
||
const linked = await screenApi.detectLinkedModals(sourceScreen.screenId);
|
||
console.log("✅ 연결된 모달 화면 감지 결과:", linked);
|
||
|
||
// 초기 newScreenName 설정
|
||
setLinkedScreens(
|
||
linked.map((screen) => ({
|
||
...screen,
|
||
newScreenName: `${screen.screenName} (복사본)`,
|
||
}))
|
||
);
|
||
|
||
if (linked.length > 0) {
|
||
toast.info(`${linked.length}개의 연결된 모달 화면을 감지했습니다.`);
|
||
console.log("🎉 감지된 화면들:", linked);
|
||
} else {
|
||
console.log("ℹ️ 연결된 모달 화면이 없습니다.");
|
||
}
|
||
} catch (error) {
|
||
console.error("❌ 연결된 화면 감지 실패:", error);
|
||
// 에러가 나도 진행 가능하도록 무시
|
||
} finally {
|
||
setLoadingLinkedScreens(false);
|
||
}
|
||
};
|
||
|
||
// 화면 코드 자동 생성 (메인 + 모달 화면들) - 일괄 생성으로 중복 방지
|
||
const generateScreenCodes = async () => {
|
||
if (!targetCompanyCode) {
|
||
console.log("❌ targetCompanyCode가 없어서 화면 코드 생성 중단");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 메인 화면 1개 + 연결된 모달 화면들 = 총 개수
|
||
const totalCount = 1 + linkedScreens.length;
|
||
console.log(`📡 화면 코드 일괄 생성 API 호출: ${targetCompanyCode}, 개수: ${totalCount}`);
|
||
|
||
// 한 번에 모든 코드 생성 (중복 방지)
|
||
const generatedCodes = await screenApi.generateMultipleScreenCodes(
|
||
targetCompanyCode,
|
||
totalCount
|
||
);
|
||
console.log("✅ 생성된 화면 코드들:", generatedCodes);
|
||
|
||
// 첫 번째 코드는 메인 화면용
|
||
setScreenCode(generatedCodes[0]);
|
||
console.log("✅ 메인 화면 코드:", generatedCodes[0]);
|
||
|
||
// 나머지 코드들은 모달 화면들에 순서대로 할당
|
||
if (linkedScreens.length > 0) {
|
||
const updatedLinkedScreens = linkedScreens.map((screen, index) => ({
|
||
...screen,
|
||
newScreenCode: generatedCodes[index + 1], // 1번째부터 시작
|
||
}));
|
||
|
||
setLinkedScreens(updatedLinkedScreens);
|
||
console.log("✅ 모달 화면 코드 할당 완료:", updatedLinkedScreens.map(s => ({
|
||
name: s.screenName,
|
||
code: s.newScreenCode
|
||
})));
|
||
}
|
||
} catch (error) {
|
||
console.error("❌ 화면 코드 일괄 생성 실패:", error);
|
||
toast.error("화면 코드 생성에 실패했습니다.");
|
||
}
|
||
};
|
||
|
||
// 연결된 화면 이름 변경
|
||
const updateLinkedScreenName = (screenId: number, newName: string) => {
|
||
setLinkedScreens((prev) =>
|
||
prev.map((screen) =>
|
||
screen.screenId === screenId ? { ...screen, newScreenName: newName } : screen
|
||
)
|
||
);
|
||
};
|
||
|
||
// 연결된 화면 제거 (복사하지 않음)
|
||
const removeLinkedScreen = (screenId: number) => {
|
||
setLinkedScreens((prev) => prev.filter((screen) => screen.screenId !== screenId));
|
||
};
|
||
|
||
// 화면명 일괄 변경 적용
|
||
const applyBulkRename = (originalName: string): string => {
|
||
if (!useBulkRename) return originalName;
|
||
|
||
let newName = originalName;
|
||
|
||
// 1. 제거할 텍스트 제거
|
||
if (removeText.trim()) {
|
||
newName = newName.replace(new RegExp(removeText.trim(), "g"), "");
|
||
newName = newName.trim(); // 앞뒤 공백 제거
|
||
}
|
||
|
||
// 2. 접두사 추가
|
||
if (addPrefix.trim()) {
|
||
newName = addPrefix.trim() + " " + newName;
|
||
}
|
||
|
||
return newName;
|
||
};
|
||
|
||
// 미리보기: 변경될 화면명들
|
||
const getPreviewNames = () => {
|
||
if (!sourceScreen || !useBulkRename) return null;
|
||
|
||
return {
|
||
main: {
|
||
original: sourceScreen.screenName,
|
||
preview: applyBulkRename(sourceScreen.screenName), // (복사본) 없음
|
||
},
|
||
modals: linkedScreens.map((screen) => ({
|
||
original: screen.screenName,
|
||
preview: applyBulkRename(screen.screenName),
|
||
})),
|
||
};
|
||
};
|
||
|
||
// 화면 복사 실행
|
||
const handleCopy = async () => {
|
||
if (!sourceScreen) return;
|
||
|
||
// 입력값 검증
|
||
if (!screenName.trim()) {
|
||
toast.error("화면명을 입력해주세요.");
|
||
return;
|
||
}
|
||
|
||
if (!screenCode.trim()) {
|
||
toast.error("화면 코드 생성에 실패했습니다. 잠시 후 다시 시도해주세요.");
|
||
return;
|
||
}
|
||
|
||
// 연결된 화면들의 이름 검증
|
||
for (const linked of linkedScreens) {
|
||
if (!linked.newScreenName?.trim()) {
|
||
toast.error(`"${linked.screenName}" 모달 화면의 새 이름을 입력해주세요.`);
|
||
return;
|
||
}
|
||
if (!linked.newScreenCode?.trim()) {
|
||
toast.error(`"${linked.screenName}" 모달 화면의 코드가 생성되지 않았습니다.`);
|
||
return;
|
||
}
|
||
}
|
||
|
||
try {
|
||
setIsCopying(true);
|
||
|
||
// 화면명 중복 체크
|
||
const companyCode = targetCompanyCode || sourceScreen.companyCode;
|
||
|
||
// 메인 화면명 중복 체크
|
||
const isMainDuplicate = await screenApi.checkDuplicateScreenName(
|
||
companyCode,
|
||
screenName.trim()
|
||
);
|
||
if (isMainDuplicate) {
|
||
toast.error(`"${screenName}" 화면명이 이미 존재합니다. 다른 이름을 입력해주세요.`);
|
||
setIsCopying(false);
|
||
return;
|
||
}
|
||
|
||
// 모달 화면명 중복 체크
|
||
for (const linked of linkedScreens) {
|
||
const isModalDuplicate = await screenApi.checkDuplicateScreenName(
|
||
companyCode,
|
||
linked.newScreenName!.trim()
|
||
);
|
||
if (isModalDuplicate) {
|
||
toast.error(
|
||
`"${linked.newScreenName}" 화면명이 이미 존재합니다. 모달 화면의 이름을 변경해주세요.`
|
||
);
|
||
setIsCopying(false);
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 메인 화면 + 모달 화면들 일괄 복사
|
||
const result = await screenApi.copyScreenWithModals(sourceScreen.screenId, {
|
||
targetCompanyCode: targetCompanyCode || undefined, // 최고 관리자: 대상 회사 전달
|
||
mainScreen: {
|
||
screenName: screenName.trim(),
|
||
screenCode: screenCode.trim(),
|
||
description: description.trim(),
|
||
},
|
||
modalScreens: linkedScreens.map((screen) => ({
|
||
sourceScreenId: screen.screenId,
|
||
screenName: screen.newScreenName!.trim(),
|
||
screenCode: screen.newScreenCode!.trim(),
|
||
})),
|
||
});
|
||
|
||
console.log("✅ 복사 완료:", result);
|
||
|
||
toast.success(
|
||
`화면 복사가 완료되었습니다! (메인 1개 + 모달 ${result.modalScreens.length}개)`
|
||
);
|
||
|
||
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("");
|
||
setTargetCompanyCode("");
|
||
setLinkedScreens([]);
|
||
onClose();
|
||
};
|
||
|
||
return (
|
||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||
<DialogContent className="max-w-[95vw] sm:max-w-[700px] max-h-[90vh] overflow-hidden">
|
||
<DialogHeader>
|
||
<DialogTitle className="flex items-center gap-2">
|
||
<Copy className="h-5 w-5" />
|
||
화면 복사
|
||
{linkedScreens.length > 0 && (
|
||
<span className="text-sm font-normal text-muted-foreground">
|
||
({linkedScreens.length}개의 모달 화면 포함)
|
||
</span>
|
||
)}
|
||
</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-muted-foreground">
|
||
<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>
|
||
|
||
{/* 최고 관리자: 대상 회사 선택 */}
|
||
{isSuperAdmin && (
|
||
<div>
|
||
<Label htmlFor="targetCompany">복사 대상 회사 * (최고 관리자 전용)</Label>
|
||
<Select value={targetCompanyCode} onValueChange={setTargetCompanyCode}>
|
||
<SelectTrigger id="targetCompany" className="mt-1">
|
||
<SelectValue placeholder="회사를 선택하세요" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{loadingCompanies ? (
|
||
<div className="p-2 text-center text-sm text-muted-foreground">
|
||
로딩 중...
|
||
</div>
|
||
) : (
|
||
companies.map((company) => (
|
||
<SelectItem key={company.companyCode} value={company.companyCode}>
|
||
{company.companyName} ({company.companyCode})
|
||
</SelectItem>
|
||
))
|
||
)}
|
||
</SelectContent>
|
||
</Select>
|
||
<p className="mt-1 text-xs text-muted-foreground">
|
||
선택한 회사로 화면이 복사됩니다. 원본과 다른 회사를 선택하면 회사 간 화면 복사가 가능합니다.
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* 화면명 일괄 수정 */}
|
||
<div className="rounded-lg border border-blue-200 bg-blue-50 p-4">
|
||
<div className="mb-3 flex items-center gap-2">
|
||
<input
|
||
type="checkbox"
|
||
id="useBulkRename"
|
||
checked={useBulkRename}
|
||
onChange={(e) => setUseBulkRename(e.target.checked)}
|
||
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-2 focus:ring-blue-500"
|
||
/>
|
||
<Label htmlFor="useBulkRename" className="text-sm font-medium text-blue-900 cursor-pointer">
|
||
🔄 화면명 일괄 수정 (선택사항)
|
||
</Label>
|
||
</div>
|
||
|
||
{useBulkRename && (
|
||
<div className="space-y-3">
|
||
<div className="grid grid-cols-2 gap-3">
|
||
<div>
|
||
<Label htmlFor="removeText" className="text-xs text-blue-900">
|
||
제거할 텍스트
|
||
</Label>
|
||
<Input
|
||
id="removeText"
|
||
value={removeText}
|
||
onChange={(e) => setRemoveText(e.target.value)}
|
||
placeholder="예: 탑씰"
|
||
className="mt-1 bg-white"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<Label htmlFor="addPrefix" className="text-xs text-blue-900">
|
||
추가할 접두사
|
||
</Label>
|
||
<Input
|
||
id="addPrefix"
|
||
value={addPrefix}
|
||
onChange={(e) => setAddPrefix(e.target.value)}
|
||
placeholder="예: 대진산업"
|
||
className="mt-1 bg-white"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 미리보기 */}
|
||
{(removeText || addPrefix) && getPreviewNames() && (
|
||
<div className="rounded-md border border-blue-300 bg-white p-3">
|
||
<p className="mb-2 text-xs font-medium text-blue-900">미리보기</p>
|
||
<div className="space-y-2 text-xs">
|
||
{/* 메인 화면 */}
|
||
<div>
|
||
<p className="text-gray-500">
|
||
메인: <span className="line-through">{getPreviewNames()?.main.original}</span>
|
||
</p>
|
||
<p className="font-medium text-blue-700">
|
||
→ {getPreviewNames()?.main.preview}
|
||
</p>
|
||
</div>
|
||
{/* 모달 화면들 */}
|
||
{getPreviewNames()?.modals.map((modal, idx) => (
|
||
<div key={idx}>
|
||
<p className="text-gray-500">
|
||
모달: <span className="line-through">{modal.original}</span>
|
||
</p>
|
||
<p className="font-medium text-blue-700">→ {modal.preview}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
<p className="text-xs text-blue-700">
|
||
💡 모든 화면명에서 "제거할 텍스트"를 삭제하고 "추가할 접두사"를 앞에 붙입니다.
|
||
</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 메인 화면 정보 입력 */}
|
||
<div className="space-y-3 rounded-lg border p-3">
|
||
<h4 className="text-sm font-medium">메인 화면 정보</h4>
|
||
|
||
<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>
|
||
|
||
{/* 연결된 모달 화면 목록 */}
|
||
{loadingLinkedScreens ? (
|
||
<div className="flex items-center justify-center gap-2 rounded-lg border p-6">
|
||
<Loader2 className="h-5 w-5 animate-spin" />
|
||
<span className="text-sm text-muted-foreground">연결된 모달 화면 감지 중...</span>
|
||
</div>
|
||
) : linkedScreens.length > 0 ? (
|
||
<div className="space-y-3 rounded-lg border p-3">
|
||
<div className="flex items-center justify-between">
|
||
<h4 className="flex items-center gap-2 text-sm font-medium">
|
||
<LinkIcon className="h-4 w-4" />
|
||
연결된 모달 화면 ({linkedScreens.length}개)
|
||
</h4>
|
||
<p className="text-xs text-muted-foreground">함께 복사됩니다</p>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
{linkedScreens.map((linkedScreen) => (
|
||
<div
|
||
key={linkedScreen.screenId}
|
||
className="space-y-2 rounded-md border bg-gray-50 p-3"
|
||
>
|
||
<div className="flex items-start justify-between gap-2">
|
||
<div className="flex-1 space-y-1">
|
||
<p className="text-xs text-muted-foreground">
|
||
원본: {linkedScreen.screenName} ({linkedScreen.screenCode})
|
||
</p>
|
||
<Input
|
||
value={linkedScreen.newScreenName || ""}
|
||
onChange={(e) =>
|
||
updateLinkedScreenName(linkedScreen.screenId, e.target.value)
|
||
}
|
||
placeholder="복사될 모달 화면 이름"
|
||
className="h-8 text-sm"
|
||
/>
|
||
<Input
|
||
value={linkedScreen.newScreenCode || ""}
|
||
readOnly
|
||
className="h-8 bg-white text-xs"
|
||
placeholder="코드 자동 생성"
|
||
/>
|
||
</div>
|
||
<Button
|
||
variant="ghost"
|
||
size="icon"
|
||
className="h-8 w-8 text-destructive hover:bg-destructive/10"
|
||
onClick={() => removeLinkedScreen(linkedScreen.screenId)}
|
||
title="이 화면은 복사하지 않음"
|
||
>
|
||
<Trash2 className="h-4 w-4" />
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<Alert>
|
||
<AlertCircle className="h-4 w-4" />
|
||
<AlertDescription className="text-xs">
|
||
모달 화면을 복사하지 않으려면 휴지통 아이콘을 클릭하세요. 버튼의 모달 연결이 자동으로
|
||
업데이트됩니다.
|
||
</AlertDescription>
|
||
</Alert>
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
|
||
<DialogFooter className="gap-2 sm:gap-0">
|
||
<Button variant="outline" onClick={handleClose} disabled={isCopying}>
|
||
취소
|
||
</Button>
|
||
<Button onClick={handleCopy} disabled={isCopying || !targetCompanyCode}>
|
||
{isCopying ? (
|
||
<>
|
||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||
복사 중...
|
||
</>
|
||
) : (
|
||
<>
|
||
<Copy className="mr-2 h-4 w-4" />
|
||
복사하기
|
||
{linkedScreens.length > 0 && ` (${linkedScreens.length + 1}개 화면)`}
|
||
</>
|
||
)}
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
);
|
||
}
|