Skip to content

Commit eedaa16

Browse files
committed
feat: nodeContent가 사용자 정의 스티커 일 때 노드 구현
1 parent 7faa33a commit eedaa16

7 files changed

Lines changed: 116 additions & 50 deletions

File tree

apps/web/src/app/(with-side-bar)/dashboard/page.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,10 @@ export default function Dashboard() {
128128
<Button
129129
variant="ghost"
130130
size="icon"
131-
className="relative"
132131
onClick={() => onActionClick("알림")}
133132
aria-label="알림"
134133
>
135134
<Bell size={20} />
136-
<div className="bg-destructive absolute -top-1 -right-1 h-2 w-2 rounded-full" />
137135
</Button>
138136
<Button
139137
variant="ghost"

apps/web/src/components/topic/custom-node.tsx

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Handle, NodeProps, NodeResizer, Position, useConnection } from "@xyflow
1313

1414
import ContentSticker from "./content-sticker";
1515
import TopicSticker from "./topic-sticker";
16+
import UserSticker from "./user-sticker";
1617

1718
interface CustomNodeProps extends NodeProps {
1819
topicId: string;
@@ -21,13 +22,14 @@ interface CustomNodeProps extends NodeProps {
2122
}
2223

2324
interface NodeData {
24-
nodeContent: "topic" | "content";
25+
nodeContent: "topic" | "content" | "sticker";
2526
item: TopicDTO | CategoryContentDTO;
2627
}
2728

2829
const stickerStyle = {
2930
topic: "from-primary bg-gradient-to-br to-blue-600 text-white",
3031
content: "bg-card hover:border-primary border-border border",
32+
sticker: "bg-yellow-50 border-yellow-300 border hover:border-yellow-400",
3133
};
3234

3335
export default function CustomNode(props: CustomNodeProps) {
@@ -45,7 +47,6 @@ export default function CustomNode(props: CustomNodeProps) {
4547

4648
const isTarget = connection.inProgress && connection.fromNode.id !== props.id;
4749
const isSource = connection.inProgress && connection.fromNode.id === props.id;
48-
const isTopic = nodeData.nodeContent === "topic";
4950

5051
const stickerClass = stickerStyle[nodeData.nodeContent];
5152

@@ -59,13 +60,21 @@ export default function CustomNode(props: CustomNodeProps) {
5960
posY: debouncedProps.positionAbsoluteY,
6061
};
6162
const updatePosition = async () => {
62-
if (isTopic) {
63-
await updateTopicPosition(body);
64-
} else {
65-
await updateContentPosition({
66-
...body,
67-
contentId: nodeData.item.id,
68-
});
63+
switch (nodeData.nodeContent) {
64+
case "topic":
65+
await updateTopicPosition(body);
66+
break;
67+
case "content":
68+
await updateContentPosition({
69+
...body,
70+
contentId: nodeData.item.id,
71+
});
72+
break;
73+
case "sticker":
74+
// TODO: 스티커 위치 변경 시 호출
75+
break;
76+
default:
77+
return;
6978
}
7079
};
7180
updatePosition();
@@ -79,13 +88,21 @@ export default function CustomNode(props: CustomNodeProps) {
7988
height: debouncedProps.height ?? 220,
8089
};
8190
const updateSize = async () => {
82-
if (isTopic) {
83-
await updateTopicSize(body);
84-
} else {
85-
await updateContentSize({
86-
...body,
87-
contentId: nodeData.item.id,
88-
});
91+
switch (nodeData.nodeContent) {
92+
case "topic":
93+
await updateTopicSize(body);
94+
break;
95+
case "content":
96+
await updateContentSize({
97+
...body,
98+
contentId: nodeData.item.id,
99+
});
100+
break;
101+
case "sticker":
102+
// TODO: 스티커 크기 변경 시 호출
103+
break;
104+
default:
105+
return;
89106
}
90107
};
91108
updateSize();
@@ -178,14 +195,16 @@ export default function CustomNode(props: CustomNodeProps) {
178195
</>
179196
)}
180197

181-
{isTopic ? (
198+
{nodeData.nodeContent === "topic" ? (
182199
<TopicSticker item={nodeData.item as TopicDTO} />
183-
) : (
200+
) : nodeData.nodeContent === "content" ? (
184201
<ContentSticker
185202
item={nodeData.item as CategoryContentDTO}
186203
isSelected={props.isSelected}
187204
onSelect={props.onSelect}
188205
/>
206+
) : (
207+
<UserSticker item={nodeData.item as TopicDTO} />
189208
)}
190209
</div>
191210
);

apps/web/src/components/topic/remove-content-button.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ interface RemoveContentButtonProps {
99
export default function RemoveContentButton({ selectedNodeIds }: RemoveContentButtonProps) {
1010
const onRemoveContent = async () => {
1111
// TODO: 변경된 삭제 API 연동
12-
console.log(selectedNodeIds);
1312
};
1413

1514
return (

apps/web/src/components/topic/summarize-dialog.tsx

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { useState } from "react";
2-
31
import {
42
Dialog,
53
DialogClose,
@@ -18,13 +16,16 @@ interface SummarizeDialogProps {
1816

1917
function SummarizeDialogContent({ selectedNodeIds }: SummarizeDialogProps) {
2018
const { close } = useDialog();
21-
const [prompt, setPrompt] = useState("");
2219

23-
const onSummarize = async () => {
20+
const onSummarize = async (e: React.FormEvent<HTMLFormElement>) => {
21+
e.preventDefault();
22+
23+
const formData = new FormData(e.currentTarget);
24+
const prompt = formData.get("prompt") as string;
25+
2426
if (!prompt.trim()) return;
2527

2628
// TODO: 요약 API 연동
27-
console.log("요약 요청:", { selectedNodeIds, prompt });
2829
close();
2930
};
3031

@@ -35,23 +36,21 @@ function SummarizeDialogContent({ selectedNodeIds }: SummarizeDialogProps) {
3536
<h2 className="text-foreground text-lg font-semibold">요약</h2>
3637
<p className="text-muted-foreground text-sm">선택한 콘텐츠를 어떻게 요약하시겠습니까?</p>
3738
</div>
38-
<div>
39+
<form onSubmit={onSummarize}>
3940
<label className="text-sm font-medium">요약 프롬프트</label>
4041
<textarea
41-
value={prompt}
42-
onChange={(e) => setPrompt(e.target.value)}
42+
name="prompt"
43+
required
4344
placeholder="예: 선택된 콘텐츠들의 핵심 내용을 요약해주세요"
4445
className="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring min-h-[100px] w-full resize-none rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
4546
/>
46-
</div>
47-
<div className="flex justify-end gap-2">
48-
<Button variant="outline" asChild>
49-
<DialogClose>취소</DialogClose>
50-
</Button>
51-
<Button onClick={onSummarize} disabled={!prompt.trim()}>
52-
요약하기
53-
</Button>
54-
</div>
47+
<div className="mt-4 flex justify-end gap-2">
48+
<Button type="button" variant="outline" asChild>
49+
<DialogClose>취소</DialogClose>
50+
</Button>
51+
<Button type="submit">요약하기</Button>
52+
</div>
53+
</form>
5554
</div>
5655
</DialogContent>
5756
);

apps/web/src/components/topic/topic-sticker.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useTopicStore } from "@/lib/zustand/topic-store";
22
import type { TopicDTO } from "@/models/topic";
33
import { Dialog, DialogTrigger } from "@repo/ui/components/dialog";
44

5-
import { Edit, Lightbulb, Trash2 } from "lucide-react";
5+
import { Edit, NotebookPen, Trash2 } from "lucide-react";
66

77
import RemoveTopicDialog from "./remove-topic-dialog";
88
import { Button } from "../ui/button";
@@ -18,14 +18,14 @@ export default function TopicSticker({ item }: { item: TopicDTO }) {
1818
return (
1919
<>
2020
<div className="mb-6 flex items-center justify-between">
21-
<div className="flex h-15 w-15 items-center justify-center rounded-2xl bg-white/20 text-2xl backdrop-blur-sm">
22-
<Lightbulb size={24} />
21+
<div className="flex size-15 shrink-0 items-center justify-center rounded-2xl bg-white/20 text-2xl backdrop-blur-sm">
22+
<NotebookPen size={24} />
2323
</div>
2424
<div className="flex gap-2">
2525
<Button
2626
variant="ghost"
2727
size="icon"
28-
className="h-9 w-9 bg-white/20 text-white hover:bg-white/30"
28+
className="size-9 bg-white/20 text-white hover:bg-white/30"
2929
onClick={onEditTopic}
3030
>
3131
<Edit size={16} />
@@ -34,7 +34,7 @@ export default function TopicSticker({ item }: { item: TopicDTO }) {
3434
<Button
3535
variant="ghost"
3636
size="icon"
37-
className="h-9 w-9 bg-white/20 text-white hover:bg-white/30"
37+
className="size-9 bg-white/20 text-white hover:bg-white/30"
3838
asChild
3939
>
4040
<DialogTrigger>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { useTopicStore } from "@/lib/zustand/topic-store";
2+
import type { TopicDTO } from "@/models/topic";
3+
import { Dialog, DialogTrigger } from "@repo/ui/components/dialog";
4+
5+
import { Edit, Sticker, Trash2 } from "lucide-react";
6+
7+
import RemoveTopicDialog from "./remove-topic-dialog";
8+
import { Button } from "../ui/button";
9+
10+
export default function UserSticker({ item }: { item: TopicDTO }) {
11+
const { setEditingTopic, setShowEditTopicSidebar } = useTopicStore();
12+
13+
const onEditTopic = () => {
14+
setEditingTopic(item);
15+
setShowEditTopicSidebar(true);
16+
};
17+
18+
return (
19+
<>
20+
<div className="mb-6 flex items-center justify-between">
21+
<div className="flex size-15 shrink-0 items-center justify-center rounded-2xl bg-yellow-300 text-yellow-900">
22+
<Sticker size={24} />
23+
</div>
24+
<div className="flex gap-2">
25+
<Button
26+
variant="ghost"
27+
size="icon"
28+
className="size-9 bg-yellow-300/50 text-yellow-900 hover:bg-yellow-300/90"
29+
onClick={onEditTopic}
30+
>
31+
<Edit size={16} />
32+
</Button>
33+
<Dialog>
34+
<Button
35+
variant="ghost"
36+
size="icon"
37+
className="size-9 bg-yellow-300/50 text-yellow-900 hover:bg-yellow-300/90"
38+
asChild
39+
>
40+
<DialogTrigger>
41+
<Trash2 size={16} />
42+
</DialogTrigger>
43+
</Button>
44+
<RemoveTopicDialog topicId={item.id} />
45+
</Dialog>
46+
</div>
47+
</div>
48+
<div>
49+
<h2 className="mb-3 text-2xl leading-tight font-bold text-gray-800">{item.title}</h2>
50+
<p
51+
className="line-clamp-2 text-lg leading-relaxed text-gray-600"
52+
dangerouslySetInnerHTML={{ __html: item.content }}
53+
/>
54+
</div>
55+
</>
56+
);
57+
}

apps/web/src/page/topic/index.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { useGetTopicById } from "@/lib/tanstack/query/topic";
1515
import { useMobileMenuStore } from "@/lib/zustand/mobile-menu-store";
1616
import { useTopicStore } from "@/lib/zustand/topic-store";
1717
import { infoToast } from "@/utils/toast";
18-
import { cn } from "@repo/ui/utils/cn";
1918
import {
2019
addEdge,
2120
Background,
@@ -29,7 +28,7 @@ import {
2928
useNodesState,
3029
} from "@xyflow/react";
3130

32-
import { AlertTriangle, Lightbulb, Loader2, Menu, Plus, Search, Sparkles } from "lucide-react";
31+
import { AlertTriangle, Lightbulb, Loader2, Menu, Plus, Search } from "lucide-react";
3332

3433
interface TopicBoardPageProps {
3534
id: string;
@@ -150,11 +149,6 @@ export default function TopicBoardPage({ id, type }: TopicBoardPageProps) {
150149
setShowNewTopicModal(true);
151150
};
152151

153-
const onSummaryClick = () => {
154-
console.log("요약 요청:", { topicId: id, nodeIds: selectedNodeIds });
155-
infoToast("요약 기능이 준비 중입니다.");
156-
};
157-
158152
const onResizeStart = (e: React.MouseEvent) => {
159153
e.preventDefault();
160154
setIsResizing(true);

0 commit comments

Comments
 (0)