import { ChangeEvent, useEffect, useMemo, useRef, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { Button } from '~/shared/components/button/Button';
import { Modal, ModalRegion } from '~/shared/components/modal';
import { Select, type SelectOption } from '~/shared/components/select/Select';
import { Tree, type TreeItemType, type TreeRef } from '~/shared/components/tree/Tree';
import { fetchFolderList, fetchRegions } from '../api/imagery';
import { useAerialChunkUpload, type UploadingFile } from '../hooks/useAerialChunkUpload';
type AerialRegisterModalProps = {
isOpen: boolean;
onClose: () => void;
onUploadSuccess?: () => void;
};
type FormValues = {
type: string;
region: string;
folderPath: string;
};
const TYPE_OPTIONS: SelectOption[] = [
{ value: 'mapFrame', label: '도곽' },
];
const formatFileSize = (bytes: number) => {
if (bytes === 0) return '0B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))}${sizes[i]}`;
};
const ProgressBar = ({ progress }: { progress: number }) => {
return (
);
};
const FileItem = ({
file,
folderPath,
onRemove,
}: {
file: UploadingFile;
folderPath: string;
onRemove: (id: string) => void;
}) => {
const fullPath = folderPath ? `${folderPath}/${file.fileName}` : file.fileName;
return (
{fullPath}
);
};
export const AerialRegisterModal = ({ isOpen, onClose, onUploadSuccess }: AerialRegisterModalProps) => {
const treeRef = useRef(null);
const fileInputRef = useRef(null);
const { control, setValue, watch, reset: resetForm } = useForm({
defaultValues: {
type: 'mapFrame',
region: '',
folderPath: '',
},
});
const selectedRegion = watch('region');
const selectedFolder = watch('folderPath');
const [regions, setRegions] = useState([]);
const [treeItems, setTreeItems] = useState([]);
const [isLoadingFolders, setIsLoadingFolders] = useState(false);
const {
files,
addFiles,
startUpload,
removeFile,
reset: resetUpload,
isUploading,
isAllCompleted,
hasFiles,
totalSize,
uploadedSize,
overallProgress,
} = useAerialChunkUpload();
const memorizedSelectedKeys = useMemo(
() => new Set(selectedFolder ? [selectedFolder] : []),
[selectedFolder]
);
// 지역 목록 조회
useEffect(() => {
const loadRegions = async () => {
try {
const data = await fetchRegions();
setRegions(data.map((r) => ({ value: r.code, label: r.name })));
} catch (error) {
console.error('Failed to load regions:', error);
}
};
if (isOpen) {
loadRegions();
}
}, [isOpen]);
// 지역 선택 시 폴더 목록 조회
useEffect(() => {
if (!selectedRegion) {
setTreeItems([]);
return;
}
const loadFolders = async () => {
setIsLoadingFolders(true);
try {
const data = await fetchFolderList(selectedRegion);
const items: TreeItemType[] = data.folders.map((folder) => ({
id: folder.fullPath,
title: folder.folderNm,
type: 'directory',
}));
setTreeItems(items);
} catch (error) {
console.error('Failed to load folders:', error);
} finally {
setIsLoadingFolders(false);
}
};
loadFolders();
}, [selectedRegion]);
const handleExpandFolder = async (key: string | number) => {
const folderPath = String(key);
try {
const data = await fetchFolderList(folderPath);
data.folders.forEach((folder) => {
const newItem: TreeItemType = {
id: folder.fullPath,
title: folder.folderNm,
type: 'directory',
};
treeRef.current?.addItem(newItem, folderPath);
});
} catch (error) {
console.error('Failed to load subfolders:', error);
}
};
const handleCollapseFolder = (key: string | number) => {
treeRef.current?.clearItemsByParentKey(key);
};
const handleFileChange = (e: ChangeEvent) => {
const selectedFiles = e.target.files;
if (!selectedFiles || selectedFiles.length === 0) return;
const fileArray = Array.from(selectedFiles);
const addedFiles = addFiles(fileArray);
// 파일 선택 즉시 업로드 시작
startUpload(addedFiles);
// input 초기화 (같은 파일 재선택 가능하도록)
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
const handleUpload = () => {
if (!selectedFolder) {
ModalRegion.alert({ content: '업로드할 폴더를 선택해주세요.' });
return;
}
if (!hasFiles) {
ModalRegion.alert({ content: '업로드할 파일을 선택해주세요.' });
return;
}
if (isUploading) {
ModalRegion.alert({ content: '파일 업로드가 진행 중입니다.' });
return;
}
if (isAllCompleted) {
ModalRegion.alert({
content: '파일 업로드가 완료되었습니다.',
onCancel: () => {
onUploadSuccess?.();
handleClose();
},
});
}
};
const handleClose = () => {
resetUpload();
resetForm();
setTreeItems([]);
onClose();
};
const handleCancel = () => {
if (isUploading || hasFiles) {
ModalRegion.confirm({
content: '업로드를 취소하시겠습니까?\n진행 중인 업로드가 중단됩니다.',
confirmText: '예',
cancelText: '아니오',
onConfirm: (close) => {
close();
handleClose();
},
});
} else {
handleClose();
}
};
const currentUploadingFile = files.find((f) => f.status === 'uploading');
return (
{() => (
)}
);
};