Skip to content

Commit 0c00664

Browse files
authored
Merge pull request #257 from boostcampwm-2024/dev
6주차 최종 배포
2 parents d3cd22f + 5cce91d commit 0c00664

File tree

7 files changed

+117
-15
lines changed

7 files changed

+117
-15
lines changed

client/index.html

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,20 @@
55
<link rel="icon" type="image/svg+xml" href="/logo.png" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<title>BooMap</title>
8+
<meta name="description" content="AI를 통한 자동 마인드맵 생성 서비스" />
9+
<meta name="author" content="부스트캠프 웹모바일 9기 Web32팀" />
10+
<meta property="og:title" content="BooMap" />
11+
<meta property="og:description" content="AI를 통한 자동 마인드맵 생성 서비스" />
12+
<meta property="og:url" content="<https://boomap.site/>" />
13+
<meta property="og:image" content="/thumbnail.png" />
14+
<meta property="og:image-alt" content="마인드맵 저작도구 BooMap의 썸네일" />
15+
<meta property="og:image:width" content="1200" />
16+
<meta property="og:image:height" content="600" />
17+
<meta property="og:site_name" content="BooMap" />
18+
<meta property="og:type" content="website" />
819
</head>
920
<body>
10-
<div id="root"></div>
21+
<div id="root" aria-label="BooMap 플랫폼"></div>
1122
<script type="module" src="/src/main.tsx"></script>
1223
</body>
1324
</html>

client/public/thumbnail.png

84.7 KB
Loading

client/src/components/Dashboard/MindMapInfoItem.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import profile from "@/assets/profile.png";
21
import useModal from "@/hooks/useModal";
32
import { Button } from "@headlessui/react";
43
import extractDate from "@/utils/extractDate";
@@ -13,9 +12,10 @@ import { useConnectionStore } from "@/store/useConnectionStore";
1312
interface MindMapInfoItemProps {
1413
data: DashBoard;
1514
index: number;
15+
onDelete: (id: number) => void;
1616
}
1717

18-
export default function MindMapInfoItem({ data, index }: MindMapInfoItemProps) {
18+
export default function MindMapInfoItem({ data, index, onDelete }: MindMapInfoItemProps) {
1919
const { open, openModal, closeModal } = useModal();
2020
const keywordData = data.keyword.slice(0, 4);
2121
const navigate = useNavigate();
@@ -31,6 +31,7 @@ export default function MindMapInfoItem({ data, index }: MindMapInfoItemProps) {
3131
function handleDelete() {
3232
mutation.mutate();
3333
closeModal();
34+
onDelete(data.id);
3435
}
3536

3637
function navigateToMindMap() {

client/src/components/Dashboard/UserDashBoard.tsx

Lines changed: 92 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
import MindMapInfoItem from "./MindMapInfoItem";
22
import { Button, Input } from "@headlessui/react";
33
import { useEffect, useState } from "react";
4-
import { FaPlus, FaSearch } from "react-icons/fa";
4+
import { FaArrowDown, FaPlus, FaArrowUp } from "react-icons/fa";
55
import { useNavigate } from "react-router-dom";
66
import useDashBoard from "@/api/fetchHooks/useDashBoard";
77
import NoMindMap from "@/components/Dashboard/NoMindMap";
88
import { useConnectionStore } from "@/store/useConnectionStore";
99
import { IoSearch } from "react-icons/io5";
1010

11+
type SortKey = "title" | "ownerName" | "createDate";
12+
type SortOrder = "asc" | "desc";
13+
1114
export default function UserDashBoard() {
1215
const { data } = useDashBoard();
13-
const [searchContent, setSearchContent] = useState("");
16+
const [filteredData, setFilteredData] = useState(data);
17+
const [sortKey, setSortKey] = useState<SortKey>("createDate");
18+
const [sortOrder, setSortOrder] = useState<SortOrder>("desc");
19+
const [searchQuery, setSearchQuery] = useState("");
1420
const navigate = useNavigate();
1521
const createConnection = useConnectionStore((state) => state.createConnection);
1622
const updateOwnedMindMap = useConnectionStore((state) => state.updateOwnedMindMap);
@@ -21,17 +27,96 @@ export default function UserDashBoard() {
2127
}
2228
}, [data]);
2329

30+
useEffect(() => {
31+
applyFilters(data, searchQuery);
32+
}, [sortKey, sortOrder]);
33+
2434
function searchData(content: string) {
25-
if (!data) return [];
2635
return data.filter((item) => item.title.includes(content) || item.keyword.some((k) => k.includes(content)));
2736
}
2837

29-
const filteredData = searchData(searchContent);
38+
function sortData(items, key: SortKey, order: SortOrder) {
39+
return [...items].sort((a, b) => {
40+
const valueA = key === "createDate" ? new Date(a[key]).getTime() : a[key];
41+
const valueB = key === "createDate" ? new Date(b[key]).getTime() : b[key];
42+
43+
if (order === "asc") return valueA < valueB ? -1 : valueA > valueB ? 1 : 0;
44+
return valueA > valueB ? -1 : valueA < valueB ? 1 : 0;
45+
});
46+
}
47+
48+
function applyFilters(originalData, query: string) {
49+
let result = query ? searchData(query) : originalData;
50+
result = sortData(result, sortKey, sortOrder);
51+
setFilteredData(result);
52+
}
53+
54+
function handleSearchChange(query: string) {
55+
setSearchQuery(query);
56+
applyFilters(data, query);
57+
}
58+
59+
function handleSortChange(key: SortKey, order: SortOrder) {
60+
setSortKey(key);
61+
setSortOrder(order);
62+
applyFilters(data, searchQuery);
63+
}
64+
65+
function handleDeleteMindMap(id: number) {
66+
setFilteredData((prevData) => prevData.filter((item) => item.id !== id));
67+
}
3068

3169
if (!data.length) return <NoMindMap />;
3270

71+
const buttonBaseClass = "flex items-center gap-0.5 text-sm";
72+
const buttonInactiveClass = "text-grayscale-300 hover:text-white";
73+
const buttonActiveClass = "text-white";
74+
3375
return (
34-
<div className="relative flex h-full w-full flex-col rounded-[20px] bg-grayscale-700 px-8 pb-24 pt-8">
76+
<div className="relative flex h-full w-full flex-col rounded-[20px] bg-grayscale-700 px-8 pb-24 pt-3">
77+
<div className="mb-2 flex items-center justify-end gap-2">
78+
<Button
79+
className={`${buttonBaseClass} ${sortKey === "createDate" && sortOrder === "desc" ? buttonActiveClass : buttonInactiveClass}`}
80+
onClick={() => handleSortChange("createDate", "desc")}
81+
>
82+
최신 순
83+
</Button>
84+
<span>|</span>
85+
<Button
86+
className={`${buttonBaseClass} ${sortKey === "createDate" && sortOrder === "asc" ? buttonActiveClass : buttonInactiveClass}`}
87+
onClick={() => handleSortChange("createDate", "asc")}
88+
>
89+
오래된 순
90+
</Button>
91+
<span>|</span>
92+
<Button
93+
className={`${buttonBaseClass} ${sortKey === "title" && sortOrder === "asc" ? buttonActiveClass : buttonInactiveClass}`}
94+
onClick={() => handleSortChange("title", "asc")}
95+
>
96+
제목 순<FaArrowUp />
97+
</Button>
98+
<span>|</span>
99+
<Button
100+
className={`${buttonBaseClass} ${sortKey === "title" && sortOrder === "desc" ? buttonActiveClass : buttonInactiveClass}`}
101+
onClick={() => handleSortChange("title", "desc")}
102+
>
103+
제목 순<FaArrowDown />
104+
</Button>
105+
<span>|</span>
106+
<Button
107+
className={`${buttonBaseClass} ${sortKey === "ownerName" && sortOrder === "asc" ? buttonActiveClass : buttonInactiveClass}`}
108+
onClick={() => handleSortChange("ownerName", "asc")}
109+
>
110+
소유자 이름 순<FaArrowUp />
111+
</Button>
112+
<span>|</span>
113+
<Button
114+
className={`${buttonBaseClass} ${sortKey === "ownerName" && sortOrder === "desc" ? buttonActiveClass : buttonInactiveClass}`}
115+
onClick={() => handleSortChange("ownerName", "desc")}
116+
>
117+
소유자 이름 순<FaArrowDown />
118+
</Button>
119+
</div>
35120
<header className="flex items-center justify-between px-3 py-2 font-bold">
36121
<div className="min-w-72 pl-2">제목</div>
37122
<div className="min-w-60 pl-8">키워드</div>
@@ -40,7 +125,7 @@ export default function UserDashBoard() {
40125
</header>
41126
<div className="no-scrollbar h-[calc(100%-40px)] overflow-y-scroll border-b-[1px] border-t-[1px] border-grayscale-500">
42127
{filteredData.map((info, i) => (
43-
<MindMapInfoItem key={`dashboard-${i}`} data={info} index={i} />
128+
<MindMapInfoItem key={`dashboard-${info.id}`} data={info} index={i} onDelete={handleDeleteMindMap} />
44129
))}
45130
</div>
46131
<div className="absolute bottom-8 right-8">
@@ -55,9 +140,7 @@ export default function UserDashBoard() {
55140
<div className="absolute right-0 top-[-47px] flex items-center gap-2 rounded-2xl bg-grayscale-700 px-4 py-2 text-grayscale-200">
56141
<Input
57142
className="w-48 bg-inherit text-xs focus:outline-none"
58-
onChange={(e) => {
59-
setSearchContent(e.target.value);
60-
}}
143+
onChange={(e) => handleSearchChange(e.target.value)}
61144
placeholder="키워드나 제목을 입력하세요"
62145
/>
63146
<IoSearch size={24} />

client/src/components/MindMapCanvas/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export default function MindMapCanvas({ showMinutes, handleShowMinutes }) {
8181
case "Escape":
8282
groupRelease();
8383
selectNode({});
84+
break;
8485
case "Tab":
8586
if (e.shiftKey) {
8687
moveToPreviousNode(data, selectedNode, selectNode);

client/src/components/Modal/ShareModal.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ interface ShareModalProps {
1010

1111
export default function ShareModal({ open, closeModal }: ShareModalProps) {
1212
const [copySuccess, setCopySuccess] = useState(false);
13-
1413
const currentUrl = window.location.href;
1514

1615
async function copyLink() {
@@ -23,15 +22,21 @@ export default function ShareModal({ open, closeModal }: ShareModalProps) {
2322
setTimeout(() => setCopySuccess(false), 2000);
2423
}
2524

25+
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
26+
e.stopPropagation();
27+
}
28+
2629
return (
2730
<Modal open={open} closeModal={closeModal}>
2831
<div className="py-4">
2932
<p className="mb-3 text-lg font-bold text-black">협업 링크</p>
3033
<Input
3134
type="text"
32-
value={currentUrl}
3335
readOnly
34-
className="pointer-events-none h-10 w-full truncate rounded-lg bg-grayscale-200 px-3 py-2 text-grayscale-500"
36+
value={currentUrl}
37+
onClick={(e) => e.currentTarget.select()}
38+
onKeyDown={handleKeyDown}
39+
className="h-10 w-full truncate rounded-lg bg-grayscale-200 px-3 py-2 text-grayscale-500 focus:border-transparent focus:outline-none"
3540
/>
3641
<Button
3742
onClick={copyLink}

client/src/store/createSocketSlice.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export const createSocketSlice: StateCreator<ConnectionStore, [], [], SocketSlic
8181
const socket = get().socket;
8282
if (socket) socket.disconnect();
8383
set({ socket: null });
84+
get().nodeError.length > 0 && set({ nodeError: [] });
8485
get().updateRole("editor");
8586
},
8687

0 commit comments

Comments
 (0)