; Please enter a commit message to explain why this merge is necessary,
; especially if it merges an updated upstream into a topic branch.
;
; Lines starting with ';' will be ignored, and an empty message aborts
; the commit.
This commit is contained in:
leeheejin
2026-01-16 18:15:21 +09:00
4 changed files with 246 additions and 78 deletions

View File

@@ -172,6 +172,7 @@ export function ScreenGroupTreeView({
const [syncStatus, setSyncStatus] = useState<SyncStatus | null>(null);
const [isSyncing, setIsSyncing] = useState(false);
const [syncDirection, setSyncDirection] = useState<"screen-to-menu" | "menu-to-screen" | "all" | null>(null);
const [syncProgress, setSyncProgress] = useState<{ message: string; detail?: string } | null>(null);
// 회사 선택 (최고 관리자용)
const { user } = useAuth();
@@ -328,14 +329,31 @@ export function ScreenGroupTreeView({
setIsSyncing(true);
setSyncDirection(direction);
setSyncProgress({
message: direction === "screen-to-menu"
? "화면관리 → 메뉴 동기화 중..."
: "메뉴 → 화면관리 동기화 중...",
detail: "데이터를 분석하고 있습니다..."
});
try {
setSyncProgress({
message: direction === "screen-to-menu"
? "화면관리 → 메뉴 동기화 중..."
: "메뉴 → 화면관리 동기화 중...",
detail: "동기화 작업을 수행하고 있습니다..."
});
const response = direction === "screen-to-menu"
? await syncScreenGroupsToMenu(targetCompanyCode)
: await syncMenuToScreenGroups(targetCompanyCode);
if (response.success) {
const data = response.data;
setSyncProgress({
message: "동기화 완료!",
detail: `생성 ${data?.created || 0}개, 연결 ${data?.linked || 0}개, 스킵 ${data?.skipped || 0}`
});
toast.success(
`동기화 완료: 생성 ${data?.created || 0}개, 연결 ${data?.linked || 0}개, 스킵 ${data?.skipped || 0}`
);
@@ -347,13 +365,17 @@ export function ScreenGroupTreeView({
setSyncStatus(statusResponse.data);
}
} else {
setSyncProgress(null);
toast.error(`동기화 실패: ${response.error || "알 수 없는 오류"}`);
}
} catch (error: any) {
setSyncProgress(null);
toast.error(`동기화 실패: ${error.message}`);
} finally {
setIsSyncing(false);
setSyncDirection(null);
// 3초 후 진행 메시지 초기화
setTimeout(() => setSyncProgress(null), 3000);
}
};
@@ -366,27 +388,42 @@ export function ScreenGroupTreeView({
setIsSyncing(true);
setSyncDirection("all");
setSyncProgress({
message: "전체 회사 동기화 중...",
detail: "모든 회사의 데이터를 분석하고 있습니다..."
});
try {
setSyncProgress({
message: "전체 회사 동기화 중...",
detail: "양방향 동기화 작업을 수행하고 있습니다..."
});
const response = await syncAllCompanies();
if (response.success && response.data) {
const data = response.data;
setSyncProgress({
message: "전체 동기화 완료!",
detail: `${data.totalCompanies}개 회사, 생성 ${data.totalCreated}개, 연결 ${data.totalLinked}`
});
toast.success(
`전체 동기화 완료: ${data.totalCompanies}개 회사, 생성 ${data.totalCreated}개, 연결 ${data.totalLinked}`
);
// 그룹 데이터 새로고침
await loadGroupsData();
// 동기화 다이얼로그 닫기
setIsSyncDialogOpen(false);
} else {
setSyncProgress(null);
toast.error(`전체 동기화 실패: ${response.error || "알 수 없는 오류"}`);
}
} catch (error: any) {
setSyncProgress(null);
toast.error(`전체 동기화 실패: ${error.message}`);
} finally {
setIsSyncing(false);
setSyncDirection(null);
// 3초 후 진행 메시지 초기화
setTimeout(() => setSyncProgress(null), 3000);
}
};
@@ -979,15 +1016,17 @@ export function ScreenGroupTreeView({
<Plus className="h-4 w-4" />
</Button>
<Button
onClick={handleOpenSyncDialog}
variant="ghost"
size="sm"
className="w-full gap-2 text-muted-foreground"
>
<RefreshCw className="h-4 w-4" />
</Button>
{isSuperAdmin && (
<Button
onClick={handleOpenSyncDialog}
variant="ghost"
size="sm"
className="w-full gap-2 text-muted-foreground"
>
<RefreshCw className="h-4 w-4" />
</Button>
)}
</div>
{/* 트리 목록 */}
@@ -1816,7 +1855,23 @@ export function ScreenGroupTreeView({
{/* 메뉴-화면그룹 동기화 다이얼로그 */}
<Dialog open={isSyncDialogOpen} onOpenChange={setIsSyncDialogOpen}>
<DialogContent className="max-w-[95vw] sm:max-w-[500px]">
<DialogContent className="max-w-[95vw] sm:max-w-[500px] overflow-hidden">
{/* 동기화 진행 중 오버레이 (삭제와 동일한 스타일) */}
{isSyncing && (
<div className="absolute inset-0 z-50 flex flex-col items-center justify-center rounded-lg bg-background/90 backdrop-blur-sm">
<Loader2 className="h-10 w-10 animate-spin text-primary" />
<p className="mt-4 text-sm font-medium">{syncProgress?.message || "동기화 중..."}</p>
{syncProgress?.detail && (
<p className="mt-1 text-xs text-muted-foreground">{syncProgress.detail}</p>
)}
<div className="mt-3 h-2 w-48 overflow-hidden rounded-full bg-secondary">
<div
className="h-full bg-primary animate-pulse"
style={{ width: "100%" }}
/>
</div>
</div>
)}
<DialogHeader>
<DialogTitle className="text-base sm:text-lg">- </DialogTitle>
<DialogDescription className="text-xs sm:text-sm">