diff --git a/client/index.html b/client/index.html index 6b06a14f..11022262 100644 --- a/client/index.html +++ b/client/index.html @@ -5,9 +5,20 @@ BooMap + + + + + + + + + + + -
+
diff --git a/client/public/thumbnail.png b/client/public/thumbnail.png new file mode 100644 index 00000000..70a34948 Binary files /dev/null and b/client/public/thumbnail.png differ diff --git a/client/src/components/Dashboard/MindMapInfoItem.tsx b/client/src/components/Dashboard/MindMapInfoItem.tsx index ade0ee33..538729b5 100644 --- a/client/src/components/Dashboard/MindMapInfoItem.tsx +++ b/client/src/components/Dashboard/MindMapInfoItem.tsx @@ -1,4 +1,3 @@ -import profile from "@/assets/profile.png"; import useModal from "@/hooks/useModal"; import { Button } from "@headlessui/react"; import extractDate from "@/utils/extractDate"; @@ -13,9 +12,10 @@ import { useConnectionStore } from "@/store/useConnectionStore"; interface MindMapInfoItemProps { data: DashBoard; index: number; + onDelete: (id: number) => void; } -export default function MindMapInfoItem({ data, index }: MindMapInfoItemProps) { +export default function MindMapInfoItem({ data, index, onDelete }: MindMapInfoItemProps) { const { open, openModal, closeModal } = useModal(); const keywordData = data.keyword.slice(0, 4); const navigate = useNavigate(); @@ -31,6 +31,7 @@ export default function MindMapInfoItem({ data, index }: MindMapInfoItemProps) { function handleDelete() { mutation.mutate(); closeModal(); + onDelete(data.id); } function navigateToMindMap() { diff --git a/client/src/components/Dashboard/UserDashBoard.tsx b/client/src/components/Dashboard/UserDashBoard.tsx index 3f86ebc5..3cd6c0c9 100644 --- a/client/src/components/Dashboard/UserDashBoard.tsx +++ b/client/src/components/Dashboard/UserDashBoard.tsx @@ -1,16 +1,22 @@ import MindMapInfoItem from "./MindMapInfoItem"; import { Button, Input } from "@headlessui/react"; import { useEffect, useState } from "react"; -import { FaPlus, FaSearch } from "react-icons/fa"; +import { FaArrowDown, FaPlus, FaArrowUp } from "react-icons/fa"; import { useNavigate } from "react-router-dom"; import useDashBoard from "@/api/fetchHooks/useDashBoard"; import NoMindMap from "@/components/Dashboard/NoMindMap"; import { useConnectionStore } from "@/store/useConnectionStore"; import { IoSearch } from "react-icons/io5"; +type SortKey = "title" | "ownerName" | "createDate"; +type SortOrder = "asc" | "desc"; + export default function UserDashBoard() { const { data } = useDashBoard(); - const [searchContent, setSearchContent] = useState(""); + const [filteredData, setFilteredData] = useState(data); + const [sortKey, setSortKey] = useState("createDate"); + const [sortOrder, setSortOrder] = useState("desc"); + const [searchQuery, setSearchQuery] = useState(""); const navigate = useNavigate(); const createConnection = useConnectionStore((state) => state.createConnection); const updateOwnedMindMap = useConnectionStore((state) => state.updateOwnedMindMap); @@ -21,17 +27,96 @@ export default function UserDashBoard() { } }, [data]); + useEffect(() => { + applyFilters(data, searchQuery); + }, [sortKey, sortOrder]); + function searchData(content: string) { - if (!data) return []; return data.filter((item) => item.title.includes(content) || item.keyword.some((k) => k.includes(content))); } - const filteredData = searchData(searchContent); + function sortData(items, key: SortKey, order: SortOrder) { + return [...items].sort((a, b) => { + const valueA = key === "createDate" ? new Date(a[key]).getTime() : a[key]; + const valueB = key === "createDate" ? new Date(b[key]).getTime() : b[key]; + + if (order === "asc") return valueA < valueB ? -1 : valueA > valueB ? 1 : 0; + return valueA > valueB ? -1 : valueA < valueB ? 1 : 0; + }); + } + + function applyFilters(originalData, query: string) { + let result = query ? searchData(query) : originalData; + result = sortData(result, sortKey, sortOrder); + setFilteredData(result); + } + + function handleSearchChange(query: string) { + setSearchQuery(query); + applyFilters(data, query); + } + + function handleSortChange(key: SortKey, order: SortOrder) { + setSortKey(key); + setSortOrder(order); + applyFilters(data, searchQuery); + } + + function handleDeleteMindMap(id: number) { + setFilteredData((prevData) => prevData.filter((item) => item.id !== id)); + } if (!data.length) return ; + const buttonBaseClass = "flex items-center gap-0.5 text-sm"; + const buttonInactiveClass = "text-grayscale-300 hover:text-white"; + const buttonActiveClass = "text-white"; + return ( -
+
+
+ + | + + | + + | + + | + + | + +
제목
키워드
@@ -40,7 +125,7 @@ export default function UserDashBoard() {
{filteredData.map((info, i) => ( - + ))}
@@ -55,9 +140,7 @@ export default function UserDashBoard() {
{ - setSearchContent(e.target.value); - }} + onChange={(e) => handleSearchChange(e.target.value)} placeholder="키워드나 제목을 입력하세요" /> diff --git a/client/src/components/MindMapCanvas/index.tsx b/client/src/components/MindMapCanvas/index.tsx index 347ab011..b686e87b 100644 --- a/client/src/components/MindMapCanvas/index.tsx +++ b/client/src/components/MindMapCanvas/index.tsx @@ -81,6 +81,7 @@ export default function MindMapCanvas({ showMinutes, handleShowMinutes }) { case "Escape": groupRelease(); selectNode({}); + break; case "Tab": if (e.shiftKey) { moveToPreviousNode(data, selectedNode, selectNode); diff --git a/client/src/components/Modal/ShareModal.tsx b/client/src/components/Modal/ShareModal.tsx index 391d58f7..4baae58c 100644 --- a/client/src/components/Modal/ShareModal.tsx +++ b/client/src/components/Modal/ShareModal.tsx @@ -10,7 +10,6 @@ interface ShareModalProps { export default function ShareModal({ open, closeModal }: ShareModalProps) { const [copySuccess, setCopySuccess] = useState(false); - const currentUrl = window.location.href; async function copyLink() { @@ -23,15 +22,21 @@ export default function ShareModal({ open, closeModal }: ShareModalProps) { setTimeout(() => setCopySuccess(false), 2000); } + function handleKeyDown(e: React.KeyboardEvent) { + e.stopPropagation(); + } + return (

협업 링크

e.currentTarget.select()} + onKeyDown={handleKeyDown} + className="h-10 w-full truncate rounded-lg bg-grayscale-200 px-3 py-2 text-grayscale-500 focus:border-transparent focus:outline-none" />