"use client";
import { useState, useEffect, useCallback, useRef } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Checkbox } from "@/components/ui/checkbox";
import { useAuth } from "@/hooks/useAuth";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
DialogDescription,
} from "@/components/ui/dialog";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { cn } from "@/lib/utils";
import { MoreHorizontal, Edit, Trash2, Copy, Eye, Plus, Search, Palette, RotateCcw, Trash, Check, ChevronsUpDown } from "lucide-react";
import { ScreenDefinition } from "@/types/screen";
import { screenApi } from "@/lib/api/screen";
import { ExternalRestApiConnectionAPI, ExternalRestApiConnection } from "@/lib/api/externalRestApiConnection";
import { getScreenGroups, ScreenGroup } from "@/lib/api/screenGroup";
import { Layers } from "lucide-react";
import CreateScreenModal from "./CreateScreenModal";
import CopyScreenModal from "./CopyScreenModal";
import dynamic from "next/dynamic";
import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer";
import { DynamicWebTypeRenderer } from "@/lib/registry";
import { isFileComponent, getComponentWebType } from "@/lib/utils/componentTypeUtils";
import { TableOptionsProvider } from "@/contexts/TableOptionsContext";
import { RealtimePreview } from "./RealtimePreviewDynamic";
import { ScreenPreviewProvider } from "@/contexts/ScreenPreviewContext";
// InteractiveScreenViewer를 동적으로 import (SSR 비활성화)
const InteractiveScreenViewer = dynamic(
() => import("./InteractiveScreenViewer").then((mod) => mod.InteractiveScreenViewer),
{
ssr: false,
loading: () =>
로딩 중...
,
},
);
interface ScreenListProps {
onScreenSelect: (screen: ScreenDefinition) => void;
selectedScreen: ScreenDefinition | null;
onDesignScreen: (screen: ScreenDefinition) => void;
}
type DeletedScreenDefinition = ScreenDefinition & {
deletedDate?: Date;
deletedBy?: string;
deleteReason?: string;
};
export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScreen }: ScreenListProps) {
const { user } = useAuth();
const isSuperAdmin = user?.userType === "SUPER_ADMIN" || user?.companyCode === "*";
const [activeTab, setActiveTab] = useState("active");
const [screens, setScreens] = useState([]);
const [deletedScreens, setDeletedScreens] = useState([]);
const [loading, setLoading] = useState(true); // 초기 로딩
const [isSearching, setIsSearching] = useState(false); // 검색 중 로딩 (포커스 유지)
const [searchTerm, setSearchTerm] = useState("");
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
const [selectedCompanyCode, setSelectedCompanyCode] = useState("all");
const [companies, setCompanies] = useState([]);
const [loadingCompanies, setLoadingCompanies] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [isCreateOpen, setIsCreateOpen] = useState(false);
const [isCopyOpen, setIsCopyOpen] = useState(false);
const [screenToCopy, setScreenToCopy] = useState(null);
// 그룹 필터 관련 상태
const [selectedGroupId, setSelectedGroupId] = useState("all");
const [groups, setGroups] = useState([]);
const [loadingGroups, setLoadingGroups] = useState(false);
// 검색어 디바운스를 위한 타이머 ref
const debounceTimer = useRef(null);
// 첫 로딩 여부를 추적 (한 번만 true)
const isFirstLoad = useRef(true);
// 삭제 관련 상태
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [screenToDelete, setScreenToDelete] = useState(null);
const [deleteReason, setDeleteReason] = useState("");
const [dependencies, setDependencies] = useState<
Array<{
screenId: number;
screenName: string;
screenCode: string;
componentId: string;
componentType: string;
referenceType: string;
}>
>([]);
const [showDependencyWarning, setShowDependencyWarning] = useState(false);
const [checkingDependencies, setCheckingDependencies] = useState(false);
// 영구 삭제 관련 상태
const [permanentDeleteDialogOpen, setPermanentDeleteDialogOpen] = useState(false);
const [screenToPermanentDelete, setScreenToPermanentDelete] = useState(null);
// 휴지통 일괄삭제 관련 상태
const [selectedScreenIds, setSelectedScreenIds] = useState([]);
const [bulkDeleteDialogOpen, setBulkDeleteDialogOpen] = useState(false);
const [bulkDeleting, setBulkDeleting] = useState(false);
// 활성 화면 일괄삭제 관련 상태
const [selectedActiveScreenIds, setSelectedActiveScreenIds] = useState([]);
const [activeBulkDeleteDialogOpen, setActiveBulkDeleteDialogOpen] = useState(false);
const [activeBulkDeleteReason, setActiveBulkDeleteReason] = useState("");
const [activeBulkDeleting, setActiveBulkDeleting] = useState(false);
// 편집 관련 상태
const [editDialogOpen, setEditDialogOpen] = useState(false);
const [screenToEdit, setScreenToEdit] = useState(null);
const [editFormData, setEditFormData] = useState({
screenName: "",
description: "",
isActive: "Y",
tableName: "",
dataSourceType: "database" as "database" | "restapi",
restApiConnectionId: null as number | null,
restApiEndpoint: "",
restApiJsonPath: "data",
});
const [tables, setTables] = useState>([]);
const [loadingTables, setLoadingTables] = useState(false);
const [tableComboboxOpen, setTableComboboxOpen] = useState(false);
// REST API 연결 관련 상태 (편집용)
const [editRestApiConnections, setEditRestApiConnections] = useState([]);
const [editRestApiComboboxOpen, setEditRestApiComboboxOpen] = useState(false);
// 미리보기 관련 상태
const [previewDialogOpen, setPreviewDialogOpen] = useState(false);
const [screenToPreview, setScreenToPreview] = useState(null);
const [previewLayout, setPreviewLayout] = useState(null);
const [isLoadingPreview, setIsLoadingPreview] = useState(false);
const [previewFormData, setPreviewFormData] = useState>({});
// 최고 관리자인 경우 회사 목록 로드
useEffect(() => {
if (isSuperAdmin) {
loadCompanies();
}
}, [isSuperAdmin]);
const loadCompanies = async () => {
try {
setLoadingCompanies(true);
const { apiClient } = await import("@/lib/api/client"); // named export
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);
} finally {
setLoadingCompanies(false);
}
};
// 화면 그룹 목록 로드
useEffect(() => {
loadGroups();
}, []);
const loadGroups = async () => {
try {
setLoadingGroups(true);
const response = await getScreenGroups();
if (response.success && response.data) {
setGroups(response.data);
}
} catch (error) {
console.error("그룹 목록 조회 실패:", error);
} finally {
setLoadingGroups(false);
}
};
// 검색어 디바운스 처리 (150ms 지연 - 빠른 응답)
useEffect(() => {
// 이전 타이머 취소
if (debounceTimer.current) {
clearTimeout(debounceTimer.current);
}
// 새 타이머 설정
debounceTimer.current = setTimeout(() => {
setDebouncedSearchTerm(searchTerm);
}, 150);
// 클린업
return () => {
if (debounceTimer.current) {
clearTimeout(debounceTimer.current);
}
};
}, [searchTerm]);
// 화면 목록 로드 (실제 API) - debouncedSearchTerm 사용
useEffect(() => {
let abort = false;
const load = async () => {
try {
// 첫 로딩인 경우에만 loading=true, 그 외에는 isSearching=true
if (isFirstLoad.current) {
setLoading(true);
isFirstLoad.current = false; // 첫 로딩 완료 표시
} else {
setIsSearching(true);
}
if (activeTab === "active") {
const params: any = { page: currentPage, size: 20, searchTerm: debouncedSearchTerm };
// 최고 관리자이고 특정 회사를 선택한 경우
if (isSuperAdmin && selectedCompanyCode !== "all") {
params.companyCode = selectedCompanyCode;
}
// 그룹 필터
if (selectedGroupId !== "all") {
params.groupId = selectedGroupId;
}
console.log("🔍 화면 목록 API 호출:", params); // 디버깅용
const resp = await screenApi.getScreens(params);
console.log("✅ 화면 목록 응답:", resp); // 디버깅용
if (abort) return;
setScreens(resp.data || []);
setTotalPages(Math.max(1, Math.ceil((resp.total || 0) / 20)));
} else if (activeTab === "trash") {
const resp = await screenApi.getDeletedScreens({ page: currentPage, size: 20 });
if (abort) return;
setDeletedScreens(resp.data || []);
setTotalPages(Math.max(1, Math.ceil((resp.total || 0) / 20)));
}
} catch (e) {
console.error("화면 목록 조회 실패", e);
if (activeTab === "active") {
setScreens([]);
} else {
setDeletedScreens([]);
}
setTotalPages(1);
} finally {
if (!abort) {
setLoading(false);
setIsSearching(false);
}
}
};
load();
return () => {
abort = true;
};
}, [currentPage, debouncedSearchTerm, activeTab, selectedCompanyCode, selectedGroupId, isSuperAdmin]);
const filteredScreens = screens; // 서버 필터 기준 사용
// 화면 목록 다시 로드
const reloadScreens = async () => {
try {
setIsSearching(true);
const params: any = { page: currentPage, size: 20, searchTerm: debouncedSearchTerm };
// 최고 관리자이고 특정 회사를 선택한 경우
if (isSuperAdmin && selectedCompanyCode !== "all") {
params.companyCode = selectedCompanyCode;
}
const resp = await screenApi.getScreens(params);
setScreens(resp.data || []);
setTotalPages(Math.max(1, Math.ceil((resp.total || 0) / 20)));
} catch (e) {
console.error("화면 목록 조회 실패", e);
} finally {
setIsSearching(false);
}
};
const handleScreenSelect = (screen: ScreenDefinition) => {
onScreenSelect(screen);
};
const handleEdit = async (screen: ScreenDefinition) => {
setScreenToEdit(screen);
// 데이터 소스 타입 결정
const isRestApi = screen.dataSourceType === "restapi" || screen.tableName?.startsWith("_restapi_");
setEditFormData({
screenName: screen.screenName,
description: screen.description || "",
isActive: screen.isActive,
tableName: screen.tableName || "",
dataSourceType: isRestApi ? "restapi" : "database",
restApiConnectionId: (screen as any).restApiConnectionId || null,
restApiEndpoint: (screen as any).restApiEndpoint || "",
restApiJsonPath: (screen as any).restApiJsonPath || "data",
});
setEditDialogOpen(true);
// 테이블 목록 로드
try {
setLoadingTables(true);
const { tableManagementApi } = await import("@/lib/api/tableManagement");
const response = await tableManagementApi.getTableList();
if (response.success && response.data) {
// tableName과 displayName 매핑 (백엔드에서 displayName으로 라벨을 반환함)
const tableList = response.data.map((table: any) => ({
tableName: table.tableName,
tableLabel: table.displayName || table.tableName,
}));
setTables(tableList);
}
} catch (error) {
console.error("테이블 목록 조회 실패:", error);
} finally {
setLoadingTables(false);
}
// REST API 연결 목록 로드
try {
const connections = await ExternalRestApiConnectionAPI.getConnections({ is_active: "Y" });
setEditRestApiConnections(connections);
} catch (error) {
console.error("REST API 연결 목록 조회 실패:", error);
setEditRestApiConnections([]);
}
};
const handleEditSave = async () => {
if (!screenToEdit) return;
try {
// 데이터 소스 타입에 따라 업데이트 데이터 구성
const updateData: any = {
screenName: editFormData.screenName,
description: editFormData.description,
isActive: editFormData.isActive,
dataSourceType: editFormData.dataSourceType,
};
if (editFormData.dataSourceType === "database") {
updateData.tableName = editFormData.tableName;
updateData.restApiConnectionId = null;
updateData.restApiEndpoint = null;
updateData.restApiJsonPath = null;
} else {
// REST API
updateData.tableName = `_restapi_${editFormData.restApiConnectionId}`;
updateData.restApiConnectionId = editFormData.restApiConnectionId;
updateData.restApiEndpoint = editFormData.restApiEndpoint;
updateData.restApiJsonPath = editFormData.restApiJsonPath || "data";
}
console.log("📤 화면 편집 저장 요청:", {
screenId: screenToEdit.screenId,
editFormData,
updateData,
});
// 화면 정보 업데이트 API 호출
await screenApi.updateScreenInfo(screenToEdit.screenId, updateData);
// 선택된 테이블의 라벨 찾기
const selectedTable = tables.find((t) => t.tableName === editFormData.tableName);
const tableLabel = selectedTable?.tableLabel || editFormData.tableName;
// 목록에서 해당 화면 정보 업데이트
setScreens((prev) =>
prev.map((s) =>
s.screenId === screenToEdit.screenId
? {
...s,
screenName: editFormData.screenName,
tableName: updateData.tableName,
tableLabel: tableLabel,
description: editFormData.description,
isActive: editFormData.isActive,
dataSourceType: editFormData.dataSourceType,
}
: s,
),
);
setEditDialogOpen(false);
setScreenToEdit(null);
} catch (error) {
console.error("화면 정보 업데이트 실패:", error);
alert("화면 정보 업데이트에 실패했습니다.");
}
};
const handleDelete = async (screen: ScreenDefinition) => {
setScreenToDelete(screen);
setCheckingDependencies(true);
try {
// 의존성 체크
const dependencyResult = await screenApi.checkScreenDependencies(screen.screenId);
if (dependencyResult.hasDependencies) {
setDependencies(dependencyResult.dependencies);
setShowDependencyWarning(true);
} else {
setDeleteDialogOpen(true);
}
} catch (error) {
// console.error("의존성 체크 실패:", error);
// 의존성 체크 실패 시에도 삭제 다이얼로그는 열어줌
setDeleteDialogOpen(true);
} finally {
setCheckingDependencies(false);
}
};
const confirmDelete = async (force: boolean = false) => {
if (!screenToDelete) return;
try {
await screenApi.deleteScreen(screenToDelete.screenId, deleteReason, force);
setScreens((prev) => prev.filter((s) => s.screenId !== screenToDelete.screenId));
setDeleteDialogOpen(false);
setShowDependencyWarning(false);
setScreenToDelete(null);
setDeleteReason("");
setDependencies([]);
} catch (error: any) {
// console.error("화면 삭제 실패:", error);
// 의존성 오류인 경우 경고창 표시
if (error.response?.status === 409 && error.response?.data?.code === "SCREEN_HAS_DEPENDENCIES") {
setDependencies(error.response.data.dependencies || []);
setShowDependencyWarning(true);
setDeleteDialogOpen(false);
} else {
alert("화면 삭제에 실패했습니다.");
}
}
};
const handleCancelDelete = () => {
setDeleteDialogOpen(false);
setShowDependencyWarning(false);
setScreenToDelete(null);
setDeleteReason("");
setDependencies([]);
};
const handleRestore = async (screen: DeletedScreenDefinition) => {
if (!confirm(`"${screen.screenName}" 화면을 복원하시겠습니까?`)) return;
try {
await screenApi.restoreScreen(screen.screenId);
setDeletedScreens((prev) => prev.filter((s) => s.screenId !== screen.screenId));
// 활성 탭으로 이동하여 복원된 화면 확인
setActiveTab("active");
reloadScreens();
} catch (error) {
// console.error("화면 복원 실패:", error);
alert("화면 복원에 실패했습니다.");
}
};
const handlePermanentDelete = (screen: DeletedScreenDefinition) => {
setScreenToPermanentDelete(screen);
setPermanentDeleteDialogOpen(true);
};
const confirmPermanentDelete = async () => {
if (!screenToPermanentDelete) return;
try {
await screenApi.permanentDeleteScreen(screenToPermanentDelete.screenId);
setDeletedScreens((prev) => prev.filter((s) => s.screenId !== screenToPermanentDelete.screenId));
setPermanentDeleteDialogOpen(false);
setScreenToPermanentDelete(null);
} catch (error) {
// console.error("화면 영구 삭제 실패:", error);
alert("화면 영구 삭제에 실패했습니다.");
}
};
// 휴지통 체크박스 선택 처리
const handleScreenCheck = (screenId: number, checked: boolean) => {
if (checked) {
setSelectedScreenIds((prev) => [...prev, screenId]);
} else {
setSelectedScreenIds((prev) => prev.filter((id) => id !== screenId));
}
};
// 휴지통 전체 선택/해제
const handleSelectAll = (checked: boolean) => {
if (checked) {
setSelectedScreenIds(deletedScreens.map((screen) => screen.screenId));
} else {
setSelectedScreenIds([]);
}
};
// 휴지통 일괄삭제 실행
const handleBulkDelete = () => {
if (selectedScreenIds.length === 0) {
alert("삭제할 화면을 선택해주세요.");
return;
}
setBulkDeleteDialogOpen(true);
};
// 활성 화면 체크박스 선택 처리
const handleActiveScreenCheck = (screenId: number, checked: boolean) => {
if (checked) {
setSelectedActiveScreenIds((prev) => [...prev, screenId]);
} else {
setSelectedActiveScreenIds((prev) => prev.filter((id) => id !== screenId));
}
};
// 활성 화면 전체 선택/해제
const handleActiveSelectAll = (checked: boolean) => {
if (checked) {
setSelectedActiveScreenIds(screens.map((screen) => screen.screenId));
} else {
setSelectedActiveScreenIds([]);
}
};
// 활성 화면 일괄삭제 실행
const handleActiveBulkDelete = () => {
if (selectedActiveScreenIds.length === 0) {
alert("삭제할 화면을 선택해주세요.");
return;
}
setActiveBulkDeleteDialogOpen(true);
};
// 활성 화면 일괄삭제 확인
const confirmActiveBulkDelete = async () => {
if (selectedActiveScreenIds.length === 0) return;
try {
setActiveBulkDeleting(true);
const result = await screenApi.bulkDeleteScreens(
selectedActiveScreenIds,
activeBulkDeleteReason || undefined,
true // 강제 삭제 (의존성 무시)
);
// 삭제된 화면들을 목록에서 제거
setScreens((prev) => prev.filter((screen) => !selectedActiveScreenIds.includes(screen.screenId)));
setSelectedActiveScreenIds([]);
setActiveBulkDeleteDialogOpen(false);
setActiveBulkDeleteReason("");
// 결과 메시지 표시
let message = `${result.deletedCount}개 화면이 휴지통으로 이동되었습니다.`;
if (result.skippedCount > 0) {
message += `\n${result.skippedCount}개 화면은 삭제되지 않았습니다.`;
}
if (result.errors.length > 0) {
message += `\n오류 발생: ${result.errors.map((e) => `화면 ${e.screenId}: ${e.error}`).join(", ")}`;
}
alert(message);
} catch (error) {
console.error("일괄 삭제 실패:", error);
alert("일괄 삭제에 실패했습니다.");
} finally {
setActiveBulkDeleting(false);
}
};
const confirmBulkDelete = async () => {
if (selectedScreenIds.length === 0) return;
try {
setBulkDeleting(true);
const result = await screenApi.bulkPermanentDeleteScreens(selectedScreenIds);
// 삭제된 화면들을 목록에서 제거
setDeletedScreens((prev) => prev.filter((screen) => !selectedScreenIds.includes(screen.screenId)));
setSelectedScreenIds([]);
setBulkDeleteDialogOpen(false);
// 결과 메시지 표시
let message = `${result.deletedCount}개 화면이 영구 삭제되었습니다.`;
if (result.skippedCount > 0) {
message += `\n${result.skippedCount}개 화면은 삭제되지 않았습니다.`;
}
if (result.errors.length > 0) {
message += `\n오류 발생: ${result.errors.map((e) => `화면 ${e.screenId}: ${e.error}`).join(", ")}`;
}
alert(message);
} catch (error) {
// console.error("일괄 삭제 실패:", error);
alert("일괄 삭제에 실패했습니다.");
} finally {
setBulkDeleting(false);
}
};
const handleCopy = (screen: ScreenDefinition) => {
setScreenToCopy(screen);
setIsCopyOpen(true);
};
const handleView = async (screen: ScreenDefinition) => {
setScreenToPreview(screen);
setPreviewLayout(null); // 이전 레이아웃 초기화
setIsLoadingPreview(true);
setPreviewDialogOpen(true); // 모달 먼저 열기
// 모달이 열린 후에 레이아웃 로드
setTimeout(async () => {
try {
// 화면 레이아웃 로드
const layoutData = await screenApi.getLayout(screen.screenId);
console.log("📊 미리보기 레이아웃 로드:", layoutData);
setPreviewLayout(layoutData);
} catch (error) {
console.error("❌ 레이아웃 로드 실패:", error);
toast.error("화면 레이아웃을 불러오는데 실패했습니다.");
} finally {
setIsLoadingPreview(false);
}
}, 100); // 100ms 딜레이로 모달 애니메이션이 먼저 시작되도록
};
const handleCopySuccess = () => {
// 복사 성공 후 화면 목록 다시 로드
reloadScreens();
};
if (loading) {
return (
로딩 중...
);
}
return (
{/* 검색 및 필터 */}
{/* 최고 관리자 전용: 회사 필터 */}
{isSuperAdmin && (
)}
{/* 그룹 필터 */}
{/* 검색 입력 */}
setSearchTerm(e.target.value)}
className="h-10 pl-10 text-sm"
disabled={activeTab === "trash"}
/>
{/* 검색 중 인디케이터 */}
{isSearching && (
)}
{/* 탭 구조 */}
{
setActiveTab(value);
// 탭 전환 시 선택 상태 초기화
setSelectedActiveScreenIds([]);
setSelectedScreenIds([]);
}}>
활성 화면휴지통
{/* 활성 화면 탭 */}
{/* 선택 삭제 헤더 (선택된 항목이 있을 때만 표시) */}
{selectedActiveScreenIds.length > 0 && (
handleScreenCheck(screen.screenId, checked as boolean)}
className="mt-1"
aria-label={`${screen.screenName} 선택`}
/>
{screen.screenName}
{/* 설명 */}
{screen.description &&
{screen.description}
}
{/* 정보 */}
테이블{screen.tableLabel || screen.tableName}
삭제일{screen.deletedDate?.toLocaleDateString()}
삭제자{screen.deletedBy}
{screen.deleteReason && (
삭제 사유{screen.deleteReason}
)}
{/* 액션 */}
))}
{deletedScreens.length === 0 && (
휴지통이 비어있습니다.
)}
{/* 페이지네이션 */}
{totalPages > 1 && (
{currentPage} / {totalPages}
)}
{/* 새 화면 생성 모달 */}
{
// 목록에 즉시 반영 (첫 페이지 기준 상단 추가)
setScreens((prev) => [created, ...prev]);
}}
/>
{/* 화면 복사 모달 */}
setIsCopyOpen(false)}
sourceScreen={screenToCopy}
onCopySuccess={handleCopySuccess}
/>
{/* 삭제 확인 다이얼로그 */}
화면 삭제 확인
"{screenToDelete?.screenName}" 화면을 휴지통으로 이동하시겠습니까?
휴지통에서 언제든지 복원할 수 있습니다.
취소 confirmDelete(false)} variant="destructive">
휴지통으로 이동
{/* 의존성 경고 다이얼로그 */}
⚠️ 화면 삭제 경고
"{screenToDelete?.screenName}" 화면이 다른 화면에서 사용 중입니다.
이 화면을 삭제하면 아래 화면들의 버튼 기능이 작동하지 않을 수 있습니다.
취소 confirmDelete(true)}
variant="destructive"
disabled={!deleteReason.trim()}
>
강제 삭제
{/* 영구 삭제 확인 다이얼로그 */}
영구 삭제 확인
⚠️ "{screenToPermanentDelete?.screenName}" 화면을 영구적으로 삭제하시겠습니까?
이 작업은 되돌릴 수 없습니다!
모든 레이아웃 정보와 관련 데이터가 완전히 삭제됩니다.
{
setPermanentDeleteDialogOpen(false);
setScreenToPermanentDelete(null);
}}
>
취소
영구 삭제
{/* 휴지통 일괄삭제 확인 다이얼로그 */}
일괄 영구 삭제 확인
선택된 {selectedScreenIds.length}개 화면을 영구적으로 삭제하시겠습니까?
이 작업은 되돌릴 수 없습니다!
모든 레이아웃 정보와 관련 데이터가 완전히 삭제됩니다.
{
setBulkDeleteDialogOpen(false);
}}
disabled={bulkDeleting}
>
취소
{bulkDeleting ? "삭제 중..." : `${selectedScreenIds.length}개 영구 삭제`}
{/* 활성 화면 일괄삭제 확인 다이얼로그 */}
선택 화면 삭제 확인
선택된 {selectedActiveScreenIds.length}개 화면을 휴지통으로 이동하시겠습니까?
휴지통에서 언제든지 복원할 수 있습니다.
{
setActiveBulkDeleteDialogOpen(false);
setActiveBulkDeleteReason("");
}}
disabled={activeBulkDeleting}
>
취소
{activeBulkDeleting ? "삭제 중..." : `${selectedActiveScreenIds.length}개 휴지통으로 이동`}
{/* 화면 편집 다이얼로그 */}
{/* 화면 미리보기 다이얼로그 */}