# 작업 일지 (2026-10-04)

## 1. 개요
* **작업 대상**: 관리자 미디어 라이브러리(DAM) 폴더 및 이미지 관리 시스템
* **작업 목적**: SweetAlert2 전면 적용, OS 폴더 드래그 앤 드롭 업로드 및 가상 폴더 트리/그리드 간 폴더 이동·다중 선택 기능 구현

---

## 2. 주요 작업 내역

### 2.1. SweetAlert2 UI/UX 통일 및 스타일 보정
* SweetAlert2 팝업이 전역 관리자 폰트('Pretendard')를 강제 상속받도록 CSS 우선순위(`body .swal2-container *`) 보정
* 이미지 단일 삭제 및 다중 일괄 삭제 시 기본 `confirm`/`alert`를 SweetAlert2 모달 및 로딩 스피너로 전면 교체
* 실사용 중이거나 보호 잠금(🔒) 상태인 파일의 삭제 방지 피드백을 규격화된 안내 팝업으로 연동

### 2.2. 폴더명 변경(rename) RESTful 엔드포인트 통일
* `routes/admin.php`: `POST /image-folders/{folder}/rename` 라우트 등록
* `AdminImageFolderController`: 동일 부모 내 중복 검사 로직을 포함한 `rename` 액션 구현 (기존 405 Method Not Allowed 해결)
* `modal_media.js`: 이미지 파일명 변경과 동일한 URL 패턴으로 요청 처리 일원화

### 2.3. OS 폴더 드래그 앤 드롭 업로드 및 가상 폴더 자동 생성
* 1회 최대 업로드 개수를 50개로 제한하고 초과 및 실패 상세 내역을 SweetAlert2로 안내
* `DataTransferItem.webkitGetAsEntry` 및 FileSystem API 기반의 재귀적 디렉터리 순회 구현
* 윈도우 탐색기에서 이미지들이 포함된 폴더를 드롭했을 때, 최상위 폴더명을 감지(`new_folder_name`)하여 `image_folders` 테이블에 신규 생성 후 내부 이미지와 피벗 테이블 자동 매핑

### 2.4. 폴더 간 드래그 앤 드롭 이동 (계층 구조 변경)
* 백엔드: `POST /admin/image-folders/move` 라우트 및 `moveFolder` 메서드 구현
    * 자기 자신 및 하위 후손 폴더로의 순환 이동(Circular Reference) 차단 알고리즘 적용
    * 이동 대상 위치 내 동일 이름 중복 검증 후 `parent_id` 갱신
* 좌측 트리: 트리 노드 렌더링 시 `draggable="true"` 및 드래그·드롭 리스너 바인딩 누락 보완
* 우측 메인 캔버스:
    * 캔버스 내 폴더 카드(`main-folder-card`, `main-folder-row`)에 드래그 시작(`dragstart`) 및 드롭(`drop`) 이벤트 연결
    * 폴더 카드를 다른 폴더 카드 위로 끌어다 놓았을 때 해당 폴더의 하위 폴더로 위치 이동 지원
    * 이미지뿐 아니라 폴더 드롭 이벤트(`folder-move`)를 분기 처리하여 양방향 드롭 허용

### 2.5. 마우스 영역 드래그 다중 선택 (Marquee Selection) 확장
* 우측 메인 컨테이너에서 마우스 드래그 시 생성되는 선택 박스(`media-marquee-box`) 감지 대상 확장
* 이미지 카드(`.media-item-card`)뿐만 아니라 폴더 카드(`.main-folder-card`, `.main-folder-row`)도 충돌 영역 계산에 포함
* `selectedMediaFoldersMap` 상태를 추가하여 마우스 드래그로 폴더 여러 개를 한 번에 다중 선택할 수 있도록 구현

---

## 3. 핵심 수정 소스 코드 요약

### 3.1. 백엔드 폴더 이동 및 순환 참조 방지 (ImageFolderController.php)

    public function moveFolder(Request $request)
    {
        $request->validate([
            'folder_id'        => 'required|integer|exists:image_folders,id',
            'target_parent_id' => 'nullable',
        ]);

        $folderId = (int)$request->input('folder_id');$rawTargetId = $request->input('target_parent_id');$targetParentId = ($rawTargetId === null \vert{}\vert{}$rawTargetId === '0' || $rawTargetId === 0 \vert{}\vert{}$rawTargetId === '') 
            ? null 
            : (int)$rawTargetId;

        // 1. 자기 자신 및 하위 자식/손자 폴더로 이동 차단
        if ($targetParentId !== null) {$invalidTargets = $this->getAllDescendantFolderIds($folderId);
            if (in_array($targetParentId,$invalidTargets, true)) {
                return response()->json([
                    'success' => false,
                    'message' => '자기 자신 또는 하위 폴더 안으로는 이동할 수 없습니다.'
                ], 422);
            }
        }

        // 2. 이동 대상 위치 내 중복 폴더명 검사
        $sourceFolder = DB::table('image_folders')->where('id', $folderId)->first();$isDuplicate = DB::table('image_folders')
            ->where('parent_id', $targetParentId)
            ->where('name', $sourceFolder->name)
            ->where('id', '!=', $folderId)
            ->exists();

        if ($isDuplicate) {
            return response()->json([
                'success' => false,
                'message' => '이동하려는 위치에 동일한 이름의 폴더가 이미 존재합니다.'
            ], 422);
        }

        // 3. parent_id 갱신
        DB::table('image_folders')
            ->where('id', $folderId)
            ->update([
                'parent_id'  => $targetParentId,
                'updated_at' => now(),
            ]);

        return response()->json([
            'success' => true,
            'message' => '폴더 위치가 성공적으로 이동되었습니다.'
        ]);
    }

    private function getAllDescendantFolderIds(int $folderId): array
    {
        $ids = [$folderId];
        $children = DB::table('image_folders')->where('parent_id',$folderId)->pluck('id')->toArray();
        foreach ($children as$cid) {
            $ids = array_merge($ids, $this->getAllDescendantFolderIds((int)$cid));
        }
        return array_unique($ids);
    }

### 3.2. 프론트엔드 폴더 드롭 수신 핸들러 (modal_media.js)

    function handleSubFolderDrop(event, targetFolderId, element) {
        event.preventDefault();
        event.stopPropagation();
        element.classList.remove('bg-warning-subtle');

        targetFolderId = parseInt(targetFolderId, 10);

        // 1. 폴더 이동 데이터 확인 (folder-move)
        const rawText = event.dataTransfer.getData('text/plain');
        if (rawText) {
            try {
                const parsed = JSON.parse(rawText);
                if (parsed && parsed.type === 'folder-move') {
                    const folderIds = parsed.source_folder_ids || [parsed.source_folder_id];
                    folderIds.forEach(srcId => {
                        srcId = parseInt(srcId, 10);
                        if (srcId && srcId !== targetFolderId) {
                            executeMoveFolder(srcId, targetFolderId);
                        }
                    });
                    return;
                }
            } catch (err) {}
        }

        // 2. 이미지 이동 데이터 확인
        let imageIds = [];
        try {
            const jsonData = event.dataTransfer.getData('application/json');
            if (jsonData) imageIds = JSON.parse(jsonData);
        } catch (err) {
            imageIds = [];
        }

        if (!imageIds || imageIds.length === 0) {
            if (rawText) {
                imageIds = rawText.split(',').map(id => parseInt(id.trim(), 10)).filter(Boolean);
            }
        }

        if (imageIds.length > 0 && targetFolderId) {
            moveFilesToFolder(imageIds, targetFolderId);
        }
    }

---

## 4. 특이사항 및 향후 계획
* 폴더 이동 시 DB 상의 `parent_id` 변경만으로 해당 폴더에 속한 하위 이미지들의 피벗 관계가 그대로 유지되어 무결성 보장
* OS 폴더 드래그 업로드 완료 후, 새로 생성된 대상 폴더로 화면 뷰를 자동 선택·이동(포커스)시키는 편의 기능 추가 예정