diff --git a/src/components/folder/FolderCardListTest.tsx b/src/components/folder/FolderCardListTest.tsx
new file mode 100644
index 0000000..425effb
--- /dev/null
+++ b/src/components/folder/FolderCardListTest.tsx
@@ -0,0 +1,178 @@
+import {SortableContext, useSortable, verticalListSortingStrategy} from "@dnd-kit/sortable";
+import {FolderType} from "../../common/types/blog.tsx";
+import {DndContext, DragEndEvent} from "@dnd-kit/core";
+import {CSS} from "@dnd-kit/utilities";
+import {FillButton} from "../common/FillButton.tsx";
+import {MdOutlineMenu} from "react-icons/md";
+import {TextButton} from "../common/TextButton.tsx";
+import {restrictToVerticalAxis} from "@dnd-kit/modifiers";
+
+function FolderCardList({
+ folders,
+ folderEditType,
+ depth = 0,
+ editFolderId,
+ editFolderTitle,
+ setEditFolderId,
+ setEditFolderTitle,
+ handleEdit,
+ addFolder,
+ handleOnDragEnd,
+ handleDelete,
+ setSelectedFolder,
+ setOpenMoveModal
+ }: {
+ folders: FolderType[],
+ folderEditType: string,
+ depth?: number,
+ editFolderId: string,
+ editFolderTitle: string,
+ setEditFolderId: (editFolderId: string) => void,
+ setEditFolderTitle: (editFolderTitle: string) => void,
+ handleEdit: (editFolder: FolderType) => void,
+ addFolder: (targetFolder: FolderType) => void,
+ handleOnDragEnd: (event: DragEndEvent) => void,
+ handleDelete: (deleteFolder: FolderType) => void,
+ setOpenMoveModal: (openMoveModal: boolean) => void,
+ setSelectedFolder: (selectedFolder: FolderType) => void,
+}) {
+
+ return (
+
+
+ {folders.map((folder: FolderType) => (
+
+ ))}
+
+
+ );
+}
+
+function FolderCard({
+ folder,
+ folderEditType,
+ depth,
+ editFolderId,
+ editFolderTitle,
+ setEditFolderId,
+ setEditFolderTitle,
+ handleEdit,
+ addFolder,
+ handleOnDragEnd,
+ handleDelete,
+ setSelectedFolder,
+ setOpenMoveModal
+ }: {
+ folder: FolderType,
+ folderEditType: string,
+ depth: number,
+ editFolderId: string,
+ editFolderTitle: string,
+ setEditFolderId: (editFolderId: string) => void,
+ setEditFolderTitle: (editFolderTitle: string) => void,
+ handleEdit: (editFolder: FolderType) => void,
+ addFolder: (targetFolder: FolderType) => void,
+ handleOnDragEnd: (event: DragEndEvent) => void,
+ handleDelete: (deleteFolder: FolderType) => void,
+ setOpenMoveModal: (openMoveModal: boolean) => void,
+ setSelectedFolder: (selectedFolder: FolderType) => void,
+}) {
+
+ const {attributes, listeners, setNodeRef, transform, transition} = useSortable({id: folder.id});
+
+ const style = {
+ transform: CSS.Translate.toString(transform),
+ transition,
+ };
+
+ return (
+
+
+ {folder.id === editFolderId
+ ?
+
setEditFolderTitle(e.target.value)}
+ placeholder="폴더 이름"/>
+
+ setEditFolderId("")}
+ addStyle={"!bg-gray-400 hover:brightness-110"}/>
+ handleEdit({...folder, title: editFolderTitle})}
+ addStyle={`${folder.title === editFolderTitle && "opacity-50 hover:!cursor-auto"}`}
+ disabled={folder.title === editFolderTitle}/>
+
+
+ :
+
+ {folderEditType === "create" &&
+
}
+
{folder.title}
+
({folder.postCount})
+
+
+ {folderEditType === "create" && depth !== 2 &&
+ addFolder(folder)}
+ addStyle={"text-xs hover:text-lime-600"}/>
+ }
+ {folderEditType === "create" &&
+ {
+ setEditFolderId(folder.id);
+ setEditFolderTitle(folder.title);
+ }} addStyle={"text-xs hover:text-lime-600"}/>}
+ {folderEditType === "delete" &&
+ {
+ handleDelete(folder);
+ }} addStyle={"text-xs hover:text-lime-600"}/>}
+ {folderEditType === "create" &&
+ {
+ setOpenMoveModal(true);
+ setSelectedFolder(folder);
+ }} addStyle={"text-xs hover:text-lime-600"}/>}
+
+
}
+
+ {folder.subFolders.length > 0 &&
+
+
+
}
+
+ );
+}
+
+
+export default FolderCardList;
\ No newline at end of file
diff --git a/src/components/setting/SettingSideBar.tsx b/src/components/setting/SettingSideBar.tsx
index fcebca3..ee0774f 100644
--- a/src/components/setting/SettingSideBar.tsx
+++ b/src/components/setting/SettingSideBar.tsx
@@ -11,6 +11,7 @@ function SettingSideBar({setSelectedSection}: {
const tabList: TabType[] = [
{section: "profile", title: "프로필"},
{section: "folder", title: "폴더"},
+ {section: "folder-test", title: "폴더 테스트"},
{section: "post", title: "게시글"},
];
diff --git a/src/pages/setting/FolderSettingPageTest.tsx b/src/pages/setting/FolderSettingPageTest.tsx
new file mode 100644
index 0000000..b4ae09f
--- /dev/null
+++ b/src/pages/setting/FolderSettingPageTest.tsx
@@ -0,0 +1,400 @@
+import {useEffect, useRef, useState} from "react";
+import {FolderType, toFolderRequestList, toFolderTypeList} from "../../common/types/blog.tsx";
+import {FillButton} from "../../components/common/FillButton.tsx";
+import ModalLayout from "../../layout/ModalLayout.tsx";
+import {DragEndEvent} from "@dnd-kit/core";
+import {arrayMove} from "@dnd-kit/sortable";
+import FolderSelectBox from "../../components/folder/FolderSelectBox.tsx";
+import {getMemberFolders, saveAndUpdateFolder} from "../../common/apis/blog.tsx";
+import {useSelector} from "react-redux";
+import {RootState} from "../../store.tsx";
+import FolderCardListTest from "../../components/folder/FolderCardListTest.tsx";
+
+function FolderSettingPage() {
+
+ const loginState = useSelector((state: RootState) => state.loginSlice);
+
+ const [trigger, setTrigger] = useState(false);
+ const [tempId, setTempId] = useState(0);
+ const [folders, setFolders] = useState([]);
+ const [folderEditType, setFolderEditType] = useState(""); // create, delete
+
+ const handleFolderEditType = (type: string) => {
+ if (folderEditType === "create" && type === "delete"
+ && !confirm("생성/수정한 폴더 변경사항이 저장됩니다. 계속하시겠습니까?")) {
+ return;
+ }
+
+ if (folderEditType === "delete" && type === "create"
+ && !confirm("삭제한 폴더 변경사항이 저장됩니다. 계속하시겠습니까?")) {
+ return;
+ }
+
+ setFolderEditType(type);
+ }
+
+ const getTempId = () => {
+ setTempId(prev => prev + 1);
+ return `temp${tempId}`;
+ }
+
+ const addFolder = (parentFolder: FolderType | null) => {
+ const tempTitle = `폴더_${crypto.randomUUID().substring(0, 4)}`;
+
+ if (parentFolder === null) {
+ const tempId = getTempId();
+ setFolders([...folders, {id: tempId, title: tempTitle, postCount: 0, subFolders: []}]);
+ setEditFolderTitle("");
+ setEditFolderId(tempId);
+ return;
+ }
+
+ setFolders(prevFolders => getAddFolderList(prevFolders, parentFolder.id, tempTitle));
+ }
+ const getAddFolderList = (folders: FolderType[], id: string, title: string) => {
+ return folders.map((folder: FolderType): FolderType => {
+ if (folder.id === id) {
+ const tempId = getTempId();
+ setEditFolderTitle("");
+ setEditFolderId(tempId);
+ return {
+ ...folder,
+ subFolders: [...folder.subFolders, {id: tempId, title: title, postCount: 0, subFolders: []}]
+ };
+ } else if (folder.subFolders.length > 0) {
+ return {...folder, subFolders: getAddFolderList(folder.subFolders, id, title)};
+ }
+ return folder;
+ });
+ }
+
+ const [openMoveModal, setOpenMoveModal] = useState(false);
+ const [selectedFolder, setSelectedFolder] = useState(null);
+ const [editFolderId, setEditFolderId] = useState("");
+ const [editFolderTitle, setEditFolderTitle] = useState("");
+ const [targetFolder, setTargetFolder] = useState({
+ id: crypto.randomUUID(),
+ title: "",
+ postCount: 0,
+ subFolders: [],
+ });
+ const [folderMoveType, setFolderMoveType] = useState(0);
+ const folderMoveTypes = ["폴더 위로 옮깁니다.", "폴더 아래로 옮깁니다.", "폴더 내부로 옮깁니다."];
+
+ const handleMoveFolder = () => {
+ if (handleDisabled(folderMoveType) || !selectedFolder) {
+ alert("활성화된 동작 중에서 선택해주세요.");
+ return;
+ }
+
+ if (folderMoveType === 2) {
+ setFolders(prevFolders => {
+ const deleteFolderList = getDeleteFolderList(prevFolders, selectedFolder.id);
+ return getAddFolderModalList(deleteFolderList);
+ });
+ } else {
+ setFolders(prevFolders => {
+ const deleteFolderList = getDeleteFolderList(prevFolders, selectedFolder.id);
+ return getModalMoveFolderList(deleteFolderList);
+ });
+ }
+
+ setOpenMoveModal(false);
+ }
+ const getModalMoveFolderList = (folders: FolderType[]) => {
+ if (!selectedFolder) {
+ return [];
+ }
+
+ const targetIndex = folders.findIndex((folder) => folder.id === targetFolder.id);
+
+ if (targetIndex !== -1) {
+ const moveIndex = (folderMoveType === 0) ? targetIndex : targetIndex + 1;
+ return folders.slice(0, moveIndex).concat(selectedFolder, folders.slice(moveIndex));
+ }
+ return folders.map((folder: FolderType): FolderType => {
+ if (folder.subFolders.length > 0) {
+ return {...folder, subFolders: getModalMoveFolderList(folder.subFolders)};
+ }
+ return folder;
+ });
+ }
+ const getAddFolderModalList = (folders: FolderType[]) => {
+ if (!selectedFolder) {
+ return [];
+ }
+
+ return folders.map((folder: FolderType): FolderType => {
+ if (folder.id === targetFolder.id) {
+ return {...folder, subFolders: [...folder.subFolders, selectedFolder]};
+ } else if (folder.subFolders.length > 0) {
+ return {...folder, subFolders: getAddFolderModalList(folder.subFolders)};
+ }
+ return folder;
+ });
+ }
+
+ const handleOnDragEnd = ({active, over}: DragEndEvent) => {
+ if (!over || active.id === over.id) {
+ return;
+ }
+
+ setFolders(prevFolders => getMoveFolderList(prevFolders, active.id as string, over.id as string));
+ }
+ const getMoveFolderList = (folders: FolderType[], activeId: string, overId: string) => {
+ const activeIndex = folders.findIndex(folder => folder.id === activeId);
+ const overIndex = folders.findIndex(folder => folder.id === overId);
+
+ if (activeIndex !== -1 && overIndex !== -1) {
+ return arrayMove(folders, activeIndex, overIndex);
+ }
+ return folders.map((folder: FolderType): FolderType => {
+ if (folder.subFolders.length > 0) {
+ return {...folder, subFolders: getMoveFolderList(folder.subFolders, activeId, overId)};
+ }
+ return folder;
+ });
+ }
+
+ const handleDisabled = (moveType: number) => {
+ if (targetFolder.title === "" || !selectedFolder) {
+ return true;
+ }
+
+ const maxDepth = 3;
+
+ const selectedFolderMaxDepth = getMaxFolderDepth(folders, selectedFolder.id) || 1;
+ const selectedFolderDepth = getFolderDepth(folders, selectedFolder.id) || 1;
+ const targetFolderDepth = getFolderDepth(folders, targetFolder.id) || 1;
+ if (moveType === 2) {
+ return selectedFolderMaxDepth + targetFolderDepth > maxDepth;
+ } else {
+ return selectedFolderMaxDepth - selectedFolderDepth + targetFolderDepth > maxDepth;
+ }
+ }
+
+ // 하위 폴더의 최대 깊이
+ const getMaxFolderDepth = (folders: FolderType[], folderId: string): number | null => {
+ for (const folder of folders) {
+ if (folder.id === folderId) {
+ return calculateMaxDepth(folder.subFolders);
+ }
+
+ const depth = getMaxFolderDepth(folder.subFolders, folderId);
+ if (depth !== null) {
+ return depth;
+ }
+ }
+
+ return null;
+ }
+ const calculateMaxDepth = (folders: FolderType[]): number => {
+ if (folders.length === 0) return 1;
+
+ const depths = folders.map(folder => 1 + calculateMaxDepth(folder.subFolders));
+ return Math.max(...depths);
+ };
+ // 폴더의 현재 깊이
+ const getFolderDepth = (folders: FolderType[], targetId: string, currentDepth: number = 1): number | null => {
+ for (const folder of folders) {
+ if (folder.id === targetId) {
+ return currentDepth;
+ }
+
+ const depth = getFolderDepth(folder.subFolders, targetId, currentDepth + 1);
+ if (depth !== null) {
+ return depth;
+ }
+ }
+
+ return null;
+ };
+
+ const handleEdit = (editFolder: FolderType) => {
+ if (editFolder.title.trim() === "") {
+ alert("폴더 이름을 입력해주세요.");
+ return;
+ }
+
+ const handleFolders = getHandleFolders(folders, editFolder.id);
+ if (handleFolders.findIndex(folder => folder.title === editFolderTitle.trim()) !== -1) {
+ alert("중복된 폴더 이름입니다.");
+ return;
+ }
+
+ setFolders(prevFolders => getEditFolderList(prevFolders, editFolder.id, editFolder.title));
+ setEditFolderId("");
+ }
+ const getHandleFolders = (folders: FolderType[], targetId: string): FolderType[] => {
+ for (const folder of folders) {
+ if (folder.id === targetId) {
+ return folders;
+ }
+
+ if (folder.subFolders) {
+ const subResult = getHandleFolders(folder.subFolders, targetId);
+ if (subResult.length > 0) {
+ return subResult;
+ }
+ }
+ }
+
+ return [];
+ }
+ const getEditFolderList = (folders: FolderType[], id: string, title: string) => {
+ return folders.map((folder: FolderType): FolderType => {
+ if (folder.id === id) {
+ return {...folder, title: title};
+ } else if (folder.subFolders.length > 0) {
+ return {...folder, subFolders: getEditFolderList(folder.subFolders, id, title)};
+ }
+ return folder;
+ })
+ }
+
+ const handleDelete = (deleteFolder: FolderType) => {
+ if (deleteFolder.postCount > 0) {
+ alert("게시글을 모두 이동한 후에 제거할 수 있습니다.");
+ return;
+ }
+
+ if (deleteFolder.subFolders.length > 0) {
+ alert("하위 폴더를 모두 제거한 후에 제거할 수 있습니다.");
+ return;
+ }
+
+ setFolders(prevFolders => getDeleteFolderList(prevFolders, deleteFolder.id));
+ }
+ const getDeleteFolderList = (folders: FolderType[], id: string) => {
+ if (folders.findIndex(folder => folder.id === id) !== -1) {
+ return folders.filter(folder => folder.id !== id);
+ }
+
+ return folders.map((folder: FolderType): FolderType => {
+ if (folder.subFolders.length > 0) {
+ return {...folder, subFolders: getDeleteFolderList(folder.subFolders, id)};
+ }
+ return folder;
+ })
+ }
+
+ const handleSubmit = () => {
+ if (!confirm("변경사항을 저장하시겠습니까?")) {
+ return;
+ }
+
+ saveAndUpdateFolder(toFolderRequestList(folders))
+ .then(() => {
+ alert("변경사항이 저장되었습니다.");
+ setTrigger(prev => !prev);
+ setFolderEditType("");
+ })
+ .catch(error => alert(error.response.data.message));
+ }
+
+ const modalRef = useRef(null);
+ const handleClickOutside = (event: MouseEvent) => {
+ if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
+ setOpenMoveModal(false);
+ }
+ };
+
+ useEffect(() => {
+ getMemberFolders(loginState.username)
+ .then(res => {
+ setFolders(toFolderTypeList(res.data));
+ })
+ .catch(error => alert(error.response.data.message));
+ }, [trigger]);
+
+ useEffect(() => {
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ }, []);
+
+ useEffect(() => {
+ setTargetFolder({...targetFolder, title: ""});
+ }, [openMoveModal]);
+
+ return (
+
+
폴더 관리 테스트
+
+ {folderEditType === "create" &&
+ addFolder(null)}>
+ 폴더 추가
+
+ }
+
+ {folderEditType !== "create" &&
+ handleFolderEditType("create")}
+ addStyle={"text-sm bg-lime-700"}/>}
+ {folderEditType !== "delete" &&
+ handleFolderEditType("delete")}
+ addStyle={"text-sm bg-red-500"}/>}
+ {folderEditType !== "" && }
+
+ {openMoveModal && (
+
+
+
+
+ {selectedFolder?.title} 폴더를
+
+
+
+
+
+ {
+ setOpenMoveModal(false);
+ }}/>
+
+
+
+
+ )}
+
+ );
+}
+
+export default FolderSettingPage;
\ No newline at end of file
diff --git a/src/pages/setting/SettingPage.tsx b/src/pages/setting/SettingPage.tsx
index 099c9a2..95d1900 100644
--- a/src/pages/setting/SettingPage.tsx
+++ b/src/pages/setting/SettingPage.tsx
@@ -5,6 +5,7 @@ import ProfileSettingPage from "./ProfileSettingPage.tsx";
import FolderSettingPage from "./FolderSettingPage.tsx";
import {useNavigate, useParams} from "react-router-dom";
import SettingSideBar from "../../components/setting/SettingSideBar.tsx";
+import FolderSettingPageTest from "./FolderSettingPageTest.tsx";
function SettingPage() {
@@ -28,6 +29,10 @@ function SettingPage() {
}
+ {(selectedSection === "folder-test") &&
+
+
+
}
{(selectedSection === "post") &&