diff --git a/src/common/apis/post.tsx b/src/common/apis/post.tsx index c7e5473..c0dc6d0 100644 --- a/src/common/apis/post.tsx +++ b/src/common/apis/post.tsx @@ -16,3 +16,9 @@ export const getPost = async (id: string) => export const getPosts = async (postListRequest: PostListRequest) => await axiosApi.get(`/post${postListRequestToParameter(postListRequest)}`); + +export interface FolderRequest { + id: string; + name: string; + subFolders: FolderRequest[]; +} \ No newline at end of file diff --git a/src/common/types/blog.tsx b/src/common/types/blog.tsx new file mode 100644 index 0000000..326188b --- /dev/null +++ b/src/common/types/blog.tsx @@ -0,0 +1,74 @@ +export interface FolderRequest { + id: string | null, + title: string, + depth: number, + orderIndex: number, + parentOrderIndex: number, +} + +export interface FolderType { + id: string, + title: string, + subFolders: FolderType[], +} + +export interface FolderResponse { + folderId: string, + title: string, + depth: number, + orderIndex: number, + parentFolderId: string | null, +} + +export const toFolderTypeList = (folderResponseList: FolderResponse[]) => { + const result: FolderType[] = []; + + folderResponseList.sort((a, b) => a.orderIndex - b.orderIndex) + .forEach(folderResponse => { + const folderType = toFolderType(folderResponse); + + if (folderResponse.depth === 0) { + result.push(folderType); + } else if (folderResponse.depth === 1) { + result[result.length - 1].subFolders.push(folderType); + } else if (folderResponse.depth === 2) { + const subFoldersLength = result[result.length - 1].subFolders.length; + result[result.length - 1].subFolders[subFoldersLength - 1].subFolders.push(folderType); + } + }); + + return result; +} + +const toFolderType = (folderResponse: FolderResponse) => { + return { + id: folderResponse.folderId, + title: folderResponse.title, + subFolders: [], + }; +} + +export const toFolderRequestList = (folderTypeList: FolderType[]) => { + const result: FolderRequest[] = []; + + const getFolderRequestList = (folderTypeList: FolderType[], depth: number = 0, parentIndex: number = -1) => { + folderTypeList.forEach(folder => { + const id = (!folder.id.startsWith("temp") ? folder.id : null); + result.push({ + id: id, + title: folder.title, + depth: depth, + orderIndex: result.length, + parentOrderIndex: parentIndex, + }); + + if (folder.subFolders.length > 0) { + getFolderRequestList(folder.subFolders, depth + 1, result.length - 1); + } + }); + } + + getFolderRequestList(folderTypeList); + + return result; +} diff --git a/src/components/blog/CategorySelectBox.tsx b/src/components/blog/CategorySelectBox.tsx new file mode 100644 index 0000000..10916ff --- /dev/null +++ b/src/components/blog/CategorySelectBox.tsx @@ -0,0 +1,87 @@ +import {FolderType} from "../../common/types/blog.tsx"; +import {MdOutlineArrowDropDown} from "react-icons/md"; +import {useEffect, useRef, useState} from "react"; + +function CategorySelectBox({folders, depth, selectedFolder, targetFolder, setTargetFolder, center}: { + folders: FolderType[], + depth: number, + selectedFolder: FolderType, + targetFolder: FolderType, + setTargetFolder: (folder: FolderType) => void, + center?: boolean, +}) { + + const [folderOpen, setFolderOpen] = useState(false); + const handleFolderOpen = () => { + setFolderOpen(prev => !prev); + } + + const folderRef = useRef(null); + const handleClickOutside = (event: MouseEvent) => { + if (folderRef.current && !folderRef.current.contains(event.target as Node)) { + setFolderOpen(false); + } + }; + useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + return ( +
+ +
+ {folders.map((folder) => + + )} +
+
+ ); +} + +function CategorySelectCard({folder, depth, selectedFolder, setTargetFolder, setFolderOpen}: { + folder: FolderType, + depth: number, + selectedFolder: FolderType, + setTargetFolder: (folder: FolderType) => void, + setFolderOpen: (open: boolean) => void, +}) { + return folder.id !== selectedFolder.id ? ( +
+ + {folder.subFolders.length > 0 && + folder.subFolders.map((subFolder => + ))} +
+ ) : <>; +} + +export default CategorySelectBox; \ No newline at end of file diff --git a/src/components/setting/FolderAddModal.tsx b/src/components/setting/FolderAddModal.tsx index e54b4cc..ee9d235 100644 --- a/src/components/setting/FolderAddModal.tsx +++ b/src/components/setting/FolderAddModal.tsx @@ -1,9 +1,9 @@ import ModalLayout from "../../layout/ModalLayout.tsx"; import {useState} from "react"; import {FillButton} from "../common/FillButton.tsx"; -import {FolderType} from "../../pages/setting/SettingPage.tsx"; import {TextButton} from "../common/TextButton.tsx"; import {MdOutlineArrowDropDown} from "react-icons/md"; +import {FolderType} from "../../common/types/blog.tsx"; function FolderAddModal({folders, setShowFolderAddModal}: { folders: FolderType[], @@ -38,36 +38,36 @@ function FolderAddModal({folders, setShowFolderAddModal}: { {folders.map((folder) => { if (!folder.subFolders) { - return
+ return
} else { - return
{folder.subFolders.map((subFolder: FolderType) => )}
; diff --git a/src/components/setting/FolderCard.tsx b/src/components/setting/FolderCard.tsx deleted file mode 100644 index b35cacd..0000000 --- a/src/components/setting/FolderCard.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import {FolderType} from "../../pages/setting/SettingPage.tsx"; -import {DndContext, DragEndEvent} from "@dnd-kit/core"; -import {SortableContext, useSortable} from "@dnd-kit/sortable"; -import {CSS} from "@dnd-kit/utilities"; -import {MdOutlineMenu} from "react-icons/md"; -import {TextButton} from "../common/TextButton.tsx"; -import {restrictToVerticalAxis} from "@dnd-kit/modifiers"; -import {useState} from "react"; -import {FillButton} from "../common/FillButton.tsx"; - -function FolderCard({setSelectedFolder, folder, setShowModal, handleDrag, isHover, handleHover, isSub}: { - setSelectedFolder: (folder: FolderType) => void, - folder: FolderType, - setShowModal: (modal: boolean) => void, - handleDrag?: (event: DragEndEvent) => void, - isHover: boolean, - handleHover: (hover: boolean) => void, - isSub?: boolean -}) { - - const [isEdit, setIsEdit] = useState(false); - const [folderNameInput, setFolderNameInput] = useState(folder.name); - - const {attributes, listeners, setNodeRef, transform, transition} = useSortable({id: folder.id}); - - const style = { - transform: CSS.Translate.toString(transform), - transition, - }; - - const handleFolderNameChange = () => { - folder.name = folderNameInput; - setIsEdit(false); - } - - return ( -
-
{ - if (isSub) { - handleHover(true); - } - }} - onMouseLeave={() => { - if (isSub) { - handleHover(false); - } - }}> - - {(isEdit) - ? { - setFolderNameInput(e.target.value) - }}/> - :

{folder.name}

} -
- {(isSub && !isEdit) && - { - setSelectedFolder(folder); - setShowModal(true); - }}/>} - {(isEdit) - ? - : setIsEdit(true)}/>} -
-
- {folder.subFolders && ( - - - {folder.subFolders.map((subFolder) => ( - - ))} - - - )} -
- ); -} - -export default FolderCard; \ No newline at end of file diff --git a/src/components/setting/FolderCardList.tsx b/src/components/setting/FolderCardList.tsx new file mode 100644 index 0000000..610bdba --- /dev/null +++ b/src/components/setting/FolderCardList.tsx @@ -0,0 +1,163 @@ +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"; + +function FolderCardList({ + folders, + depth, + editFolderId, + editFolderTitle, + setEditFolderId, + setEditFolderTitle, + handleEdit, + addFolder, + handleOnDragEnd, + handleDelete, + setSelectedFolder, + setOpenMoveModal + }: { + folders: FolderType[], + depth: number, + editFolderId: string, + editFolderTitle: string, + setEditFolderId: (editFolderId: string) => void, + setEditFolderTitle: (editFolderTitle: string) => void, + handleEdit: (editFolder: FolderType) => void, + addFolder: (targetFolder: FolderType, editTitle: string) => 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, + depth, + editFolderId, + editFolderTitle, + setEditFolderId, + setEditFolderTitle, + handleEdit, + addFolder, + handleOnDragEnd, + handleDelete, + setSelectedFolder, + setOpenMoveModal + }: { + folder: FolderType, + depth: number, + editFolderId: string, + editFolderTitle: string, + setEditFolderId: (editFolderId: string) => void, + setEditFolderTitle: (editFolderTitle: string) => void, + handleEdit: (editFolder: FolderType) => void, + addFolder: (targetFolder: FolderType, editTitle: string) => 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})}/> +
+
+ :
+
+ +

{folder.title}

+
+
+ {depth !== 2 && + addFolder(folder, `폴더_${crypto.randomUUID().substring(0, 4)}`)} + addStyle={"text-xs hover:text-lime-600"}/> + } + { + setEditFolderId(folder.id); + setEditFolderTitle(folder.title); + }} addStyle={"text-xs hover:text-lime-600"}/> + { + handleDelete(folder); + }} addStyle={"text-xs hover:text-lime-600"}/> + { + 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/FolderMoveModal.tsx b/src/components/setting/FolderMoveModal.tsx index af0fde7..0f509d4 100644 --- a/src/components/setting/FolderMoveModal.tsx +++ b/src/components/setting/FolderMoveModal.tsx @@ -1,10 +1,10 @@ -import {FolderType} from "../../pages/setting/SettingPage.tsx"; import {FillButton} from "../common/FillButton.tsx"; import {useState} from "react"; import {TextButton} from "../common/TextButton.tsx"; import {useSelector} from "react-redux"; import {RootState} from "../../store.tsx"; import ModalLayout from "../../layout/ModalLayout.tsx"; +import {FolderType} from "../../common/types/blog.tsx"; function FolderMoveModal({selectedFolder, folders, handleFolderMove, setShowModal}: { selectedFolder: FolderType | null, @@ -27,7 +27,7 @@ function FolderMoveModal({selectedFolder, folders, handleFolderMove, setShowModa

- "{selectedFolder?.name}" 폴더를 이동할 곳을 골라주세요. + "{selectedFolder?.title}" 폴더를 이동할 곳을 골라주세요.

))}
diff --git a/src/layout/ModalLayout.tsx b/src/layout/ModalLayout.tsx index 131b304..3efe563 100644 --- a/src/layout/ModalLayout.tsx +++ b/src/layout/ModalLayout.tsx @@ -1,10 +1,14 @@ -import {ReactNode} from "react"; +import {ReactNode, Ref} from "react"; -function ModalLayout({children}: { children: ReactNode }) { +function ModalLayout({children, customRef, addStyle}: { + children: ReactNode, + customRef?: Ref, + addStyle?: string, +}) { return ( -
-
-
+
+
+
{children}
diff --git a/src/pages/post/WritePage.tsx b/src/pages/post/WritePage.tsx index a101ef3..7b2cb69 100644 --- a/src/pages/post/WritePage.tsx +++ b/src/pages/post/WritePage.tsx @@ -14,6 +14,7 @@ import {uploadImage} from "../../common/apis/image.tsx"; import {checkUUID} from "../../common/util/regex.tsx"; import {TagResponse} from "../../common/types/post.tsx"; import {sortByName} from "../../common/util/sort.tsx"; +import {FolderType} from "../../common/types/blog.tsx"; interface WritePostType { inputTag: string; @@ -22,11 +23,6 @@ interface WritePostType { content: string; } -interface FolderType { - name: string; - subFolders?: FolderType[]; -} - function WritePage() { const loginState = useSelector((state: RootState) => state.loginSlice); @@ -166,21 +162,45 @@ function WritePage() { const folderData: FolderType[] = [ { - name: faker.lorem.words(), + id: crypto.randomUUID(), + title: faker.lorem.words(), subFolders: [ - {name: faker.lorem.words()}, - {name: faker.lorem.words()} + { + id: crypto.randomUUID(), + title: faker.lorem.words(), + subFolders: [], + }, + { + id: crypto.randomUUID(), + title: faker.lorem.words(), + subFolders: [], + }, ] }, { - name: faker.lorem.words(), + id: crypto.randomUUID(), + title: faker.lorem.words(), + subFolders: [], }, { - name: faker.lorem.words(), + id: crypto.randomUUID(), + title: faker.lorem.words(), subFolders: [ - {name: faker.lorem.words()}, - {name: faker.lorem.words()}, - {name: faker.lorem.words()} + { + id: crypto.randomUUID(), + title: faker.lorem.words(), + subFolders: [], + }, + { + id: crypto.randomUUID(), + title: faker.lorem.words(), + subFolders: [], + }, + { + id: crypto.randomUUID(), + title: faker.lorem.words(), + subFolders: [], + }, ] }, ]; @@ -237,36 +257,36 @@ function WritePage() { className={`${folderOpen ? "" : "hidden"} absolute z-50 top-12 left-0 bg-white divide-y divide-gray-500 rounded-lg shadow-sm`}> {folderData.map((folder) => { if (!folder.subFolders) { - return
+ return
} else { - return
{folder.subFolders.map((subFolder: FolderType) => )}
; @@ -326,7 +346,9 @@ function WritePage() { />
{path.endsWith("/edit") - && handleDelete(id)} addStyle={"bg-red-400 hover:bg-red-700"}/>} + ? handleDelete(id)} + addStyle={"bg-red-400 hover:bg-red-700"}/> + :
} {path.endsWith("/edit") ? : } diff --git a/src/pages/setting/FolderSettingPage.tsx b/src/pages/setting/FolderSettingPage.tsx index 6991b24..b34bb39 100644 --- a/src/pages/setting/FolderSettingPage.tsx +++ b/src/pages/setting/FolderSettingPage.tsx @@ -1,56 +1,427 @@ -import {DndContext, DragEndEvent} from "@dnd-kit/core"; -import {restrictToVerticalAxis} from "@dnd-kit/modifiers"; -import {SortableContext} from "@dnd-kit/sortable"; +import {faker} from "@faker-js/faker/locale/ko"; +import {useEffect, useRef, useState} from "react"; +import {FolderResponse, FolderType, toFolderRequestList, toFolderTypeList} from "../../common/types/blog.tsx"; import {FillButton} from "../../components/common/FillButton.tsx"; -import {FolderType} from "./SettingPage.tsx"; -import FolderCard from "../../components/setting/FolderCard.tsx"; -import {OutlineButton} from "../../components/common/OutlineButton.tsx"; - -function FolderSettingPage({ - setSelectedFolder, - folders, - setShowModal, - setShowFolderAddModal, - handleDragEnd, - isHover, - handleHover, - submitFolderChange - }: { - setSelectedFolder: (folder: FolderType) => void, - folders: FolderType[], - setShowModal: (modal: boolean) => void, - setShowFolderAddModal: (modal: boolean) => void, - handleDragEnd: (event: DragEndEvent) => void, - isHover: boolean, - handleHover: (hover: boolean) => void, - submitFolderChange: () => void, -}) { +import ModalLayout from "../../layout/ModalLayout.tsx"; +import CategorySelectBox from "../../components/blog/CategorySelectBox.tsx"; +import {DragEndEvent} from "@dnd-kit/core"; +import FolderCardList from "../../components/setting/FolderCardList.tsx"; +import {arrayMove} from "@dnd-kit/sortable"; + +function FolderSettingPage() { + + const idList = Array.from({length: 17}).map(() => crypto.randomUUID()); + + const folderData: FolderResponse[] = [ + { + folderId: idList[0], + title: "DigLog의 블로그", + depth: 0, + orderIndex: 0, + parentFolderId: null, + }, + { + folderId: idList[1], + title: faker.lorem.words(), + depth: 1, + orderIndex: 1, + parentFolderId: idList[0], + }, + { + folderId: idList[2], + title: faker.lorem.words(), + depth: 2, + orderIndex: 2, + parentFolderId: idList[1], + }, + { + folderId: idList[3], + title: faker.lorem.words(), + depth: 2, + orderIndex: 3, + parentFolderId: idList[1], + }, + { + folderId: idList[4], + title: faker.lorem.words(), + depth: 0, + orderIndex: 4, + parentFolderId: null, + }, + { + folderId: idList[5], + title: faker.lorem.words(), + depth: 1, + orderIndex: 5, + parentFolderId: idList[4], + }, + { + folderId: idList[6], + title: faker.lorem.words(), + depth: 0, + orderIndex: 6, + parentFolderId: null, + }, + + { + folderId: idList[7], + title: faker.lorem.words(), + depth: 1, + orderIndex: 7, + parentFolderId: idList[6], + }, + { + folderId: idList[8], + title: faker.lorem.words(), + depth: 2, + orderIndex: 8, + parentFolderId: idList[7], + }, + { + folderId: idList[9], + title: faker.lorem.words(), + depth: 2, + orderIndex: 9, + parentFolderId: idList[7], + }, + { + folderId: idList[10], + title: faker.lorem.words(), + depth: 2, + orderIndex: 10, + parentFolderId: idList[7], + }, + { + folderId: idList[11], + title: faker.lorem.words(), + depth: 1, + orderIndex: 11, + parentFolderId: idList[6], + }, + { + folderId: idList[12], + title: faker.lorem.words(), + depth: 2, + orderIndex: 12, + parentFolderId: idList[11], + }, + { + folderId: idList[13], + title: faker.lorem.words(), + depth: 2, + orderIndex: 13, + parentFolderId: idList[11], + }, + ]; + + const [tempId, setTempId] = useState(0); + const [folders, setFolders] = useState(toFolderTypeList(folderData)); + + const getTempId = () => { + setTempId(prev => prev + 1); + return `temp${tempId}`; + } + + const addFolder = (parentFolder: FolderType | null, title: string) => { + if (parentFolder === null) { + const tempId = getTempId(); + setFolders([...folders, {id: tempId, title: title, subFolders: []}]); + setEditFolderTitle(title); + setEditFolderId(tempId); + return; + } + + setFolders(prevFolders => getAddFolderList(prevFolders, parentFolder.id, title)); + } + const getAddFolderList = (folders: FolderType[], id: string, title: string) => { + return folders.map((folder: FolderType): FolderType => { + if (folder.id === id) { + const tempId = getTempId(); + setEditFolderTitle(title); + setEditFolderId(tempId); + return {...folder, subFolders: [...folder.subFolders, {id: tempId, title: title, 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({ + id: crypto.randomUUID(), + title: "", + subFolders: [], + }); + const [editFolderId, setEditFolderId] = useState(""); + const [editFolderTitle, setEditFolderTitle] = useState(""); + const [targetFolder, setTargetFolder] = useState({ + id: crypto.randomUUID(), + title: "", + subFolders: [], + }); + const [folderMoveType, setFolderMoveType] = useState(0); + const folderMoveTypes = ["폴더 위로 옮깁니다.", "폴더 아래로 옮깁니다.", "폴더 내부로 옮깁니다."]; + + const handleMoveFolder = () => { + if (handleDisabled(folderMoveType)) { + 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[]) => { + 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[]) => { + 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 === "") { + return true; + } + + const maxDepth = 2; + + 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, currentDepth: number = 1): number | null => { + for (const folder of folders) { + if (folder.id === folderId) { + return calculateMaxDepth(folder.subFolders); + } + + const depth = getMaxFolderDepth(folder.subFolders, folder.id, currentDepth + 1); + if (depth !== null) { + return depth; + } + } + + return null; + } + const calculateMaxDepth = (folders: FolderType[]): number => { + if (folders.length === 0) return 0; + + 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; + } else if (folders.findIndex(folder => folder.title === editFolderTitle.trim()) !== -1) { + alert("중복된 폴더 이름입니다."); + return; + } + + setFolders(prevFolders => getEditFolderList(prevFolders, editFolder.id, editFolder.title)); + setEditFolderId(""); + } + 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.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; + } + + console.log(toFolderRequestList(folders)); + + alert("변경사항이 저장되었습니다."); + } + + const modalRef = useRef(null); + const handleClickOutside = (event: MouseEvent) => { + if (modalRef.current && !modalRef.current.contains(event.target as Node)) { + setOpenMoveModal(false); + } + }; + + useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + useEffect(() => { + setTargetFolder({...targetFolder, title: ""}); + }, [openMoveModal]); return (

폴더 관리

-
- - - {folders.map((folder) => ( -
- -
- ))} -
-
-
-
- setShowFolderAddModal(true)} addStyle={"font-normal"}/> - + + +
+
+ {openMoveModal && ( + +
+
+

{selectedFolder.title} 폴더를

+ +
    + {folderMoveTypes.map((moveType, index) => +
  • + setFolderMoveType(index)} + disabled={handleDisabled(index)}/> + +
  • + )} +
+
+
+ { + setOpenMoveModal(false); + }}/> + +
+
+
+ )}
); } diff --git a/src/pages/setting/SettingPage.tsx b/src/pages/setting/SettingPage.tsx index 1c592b7..c422191 100644 --- a/src/pages/setting/SettingPage.tsx +++ b/src/pages/setting/SettingPage.tsx @@ -1,161 +1,14 @@ import BasicLayout from "../../layout/BasicLayout.tsx"; import {useState} from "react"; -import {faker} from "@faker-js/faker/locale/ko"; -import {DragEndEvent} from "@dnd-kit/core"; -import {arrayMove} from "@dnd-kit/sortable"; -import {useNavigate} from "react-router-dom"; -import FolderSettingPage from "./FolderSettingPage.tsx"; import PostSettingPage from "./PostSettingPage.tsx"; import ProfileSettingPage from "./ProfileSettingPage.tsx"; -import FolderMoveModal from "../../components/setting/FolderMoveModal.tsx"; -import FolderAddModal from "../../components/setting/FolderAddModal.tsx"; - -export interface FolderType { - id: string; - name: string; - subFolders?: FolderType[]; -} +import FolderSettingPage from "./FolderSettingPage.tsx"; function SettingPage() { - const navigate = useNavigate(); - - const [showFolderModal, setShowFolderModal] = useState(false); - const [showFolderAddModal, setShowFolderAddModal] = useState(false); - const tabList = ["프로필", "폴더", "게시글"]; - const folderData: FolderType[] = [ - { - id: "1", - name: faker.lorem.words(), - subFolders: [ - {id: "4", name: faker.lorem.words()}, - {id: "5", name: faker.lorem.words()} - ] - }, - { - id: "2", - name: faker.lorem.words(), - }, - { - id: "3", - name: faker.lorem.words(), - subFolders: [ - {id: "6", name: faker.lorem.words()}, - {id: "7", name: faker.lorem.words()}, - {id: "8", name: faker.lorem.words()} - ] - }, - ]; const [selectedTab, setSelectedTab] = useState("프로필"); - const [folders, setFolders] = useState(folderData); - const [selectedFolder, setSelectedFolder] = useState(null); - const [isHover, setIsHover] = useState(false); - - const handleHover = (hover: boolean) => { - setIsHover(hover); - } - - const handleDragEnd = (event: DragEndEvent) => { - const {active, over} = event; - - if (!over || active.id === over.id) { - return; - } - - let folderIndex = -1; - let oldIndex = -1; - let newIndex = -1; - - oldIndex = folders.findIndex(folder => folder.id === active.id); - newIndex = folders.findIndex(folder => folder.id === over.id); - - if (oldIndex !== -1 && newIndex !== -1) { - setFolders(folder => { - return arrayMove(folder, oldIndex, newIndex); - }); - return; - } - - for (const folder of folders) { - if (folder.subFolders) { - oldIndex = folder.subFolders.findIndex(folder => folder.id === active.id); - newIndex = folder.subFolders.findIndex(folder => folder.id === over.id); - - if (oldIndex !== -1 && newIndex !== -1) { - folderIndex = folders.indexOf(folder); - break; - } - } - } - - if (folderIndex !== -1 && oldIndex !== -1 && newIndex !== -1) { - setFolders(currentFolders => { - const updatedSubFolders = [...currentFolders[folderIndex].subFolders!]; - const [movedFolder] = updatedSubFolders.splice(oldIndex, 1); - updatedSubFolders.splice(newIndex, 0, movedFolder); - - const updatedFolders = [...currentFolders]; - updatedFolders[folderIndex] = { - ...updatedFolders[folderIndex], - subFolders: updatedSubFolders - }; - - return updatedFolders; - }); - } - } - - const handleFolderMove = (folderId: string) => { - setFolders(prevFolders => { - const newFolders = [...prevFolders]; - let folderToMove; - let targetFolder: FolderType | undefined; - - newFolders.forEach(folder => { - if (folder.subFolders) { - const subFolderIndex = folder.subFolders.findIndex(sub => sub.id === selectedFolder?.id); - if (subFolderIndex !== -1) { - folderToMove = folder.subFolders[subFolderIndex]; - folder.subFolders.splice(subFolderIndex, 1); - } - } - - if (folder.id === folderId) { - targetFolder = folder; - } - }); - - if (!folderToMove) { - return prevFolders; - } - - if (folderId === "top") { - newFolders.push(folderToMove); - return newFolders; - } - - if (!targetFolder) { - return prevFolders; - } - - if (!targetFolder.subFolders) { - targetFolder.subFolders = []; - } - - targetFolder.subFolders.push(folderToMove); - - return newFolders; - }); - } - - const submitFolderChange = () => { - if (confirm("변경사항을 저장하시겠습니까?")) { - alert("저장되었습니다."); - navigate(0); - } - } return ( @@ -173,35 +26,19 @@ function SettingPage() { )}
+ {(selectedTab === "프로필") && +
+ +
} {(selectedTab === "폴더") && -
- +
+
} {(selectedTab === "게시글") && -
+
} - {(selectedTab === "프로필") && -
- -
}
- {showFolderModal && } - {showFolderAddModal && } ); }