diff --git a/src/app/(main)/project/workspace/[id]/page.tsx b/src/app/(main)/project/workspace/[id]/page.tsx index 73b229d..9992c24 100644 --- a/src/app/(main)/project/workspace/[id]/page.tsx +++ b/src/app/(main)/project/workspace/[id]/page.tsx @@ -296,13 +296,12 @@ export default function ProjectPage() { router.push("/"); } else { setCurrentView(view); - setShowMemoPanel(false); } }; if (tasksLoading) { return ( -
+
{[1, 2, 3].map((i) => (
@@ -318,7 +317,7 @@ export default function ProjectPage() { } return ( -
+
{/* 메인 콘텐츠 */} @@ -330,6 +329,8 @@ export default function ProjectPage() { onCreateTask={handleCreateTask} onUpdateTask={handleUpdateTask} onDeleteTask={handleDeleteTask} + onToggleMemo={()=>{setShowMemoPanel((prev)=>!prev)}} + showMemoPanel={showMemoPanel} onProjectInfoClick={() => { setShowProjectInfoPanel((prev) => !prev); setShowMemoPanel(false); }} project={{ project_id: projectId, @@ -355,6 +356,8 @@ export default function ProjectPage() { onDeleteTask={handleDeleteTask} onSelectTask={() => {}} onTaskCreated={handleRefresh} + onToggleMemo={()=>{setShowMemoPanel((prev)=>!prev)}} + showMemoPanel={showMemoPanel} onProjectInfoClick={() => { setShowProjectInfoPanel((prev) => !prev); setShowMemoPanel(false); }} /> )} @@ -387,7 +390,7 @@ export default function ProjectPage() { : "w-0 opacity-0" }`} > - + setShowMemoPanel(false)} />
diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 25c8711..ad2694b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -36,7 +36,7 @@ export default function RootLayout({
-
{children}
+
{children}
diff --git a/src/components/features/calendarView/CalendarView.tsx b/src/components/features/calendarView/CalendarView.tsx index c5329aa..51ecee2 100644 --- a/src/components/features/calendarView/CalendarView.tsx +++ b/src/components/features/calendarView/CalendarView.tsx @@ -45,6 +45,7 @@ interface CalendarViewProps { started_at?: string; ended_at?: string; } | null; + showMemoPanel?: boolean; onCreateTask?: ( taskData: Omit ) => void; @@ -52,6 +53,7 @@ interface CalendarViewProps { onDeleteTask?: (taskId: string) => void; onSelectTask?: (task: Task) => void; onTaskCreated?: () => void; + onToggleMemo?: () => void; onProjectInfoClick?: () => void; } @@ -59,11 +61,13 @@ export default function CalendarView({ tasks = [], boardId, project, + showMemoPanel, onCreateTask, onUpdateTask, onDeleteTask, onSelectTask, onTaskCreated, + onToggleMemo, onProjectInfoClick, }: CalendarViewProps) { // 프로젝트 정보 추출 @@ -296,8 +300,10 @@ export default function CalendarView({ currentDate={currentDate} eventsCount={filteredTasks.length} showHelp={showHelp} + showMemo={showMemoPanel} showFilter={showFilter} onToggleHelp={() => setShowHelp(!showHelp)} + onToggleMemo={onToggleMemo} onToggleFilter={() => setShowFilter(!showFilter)} onProjectInfoClick={onProjectInfoClick} onAddTask={() => { diff --git a/src/components/features/calendarView/components/CalendarHeader.tsx b/src/components/features/calendarView/components/CalendarHeader.tsx index e36a047..dd0a0e2 100644 --- a/src/components/features/calendarView/components/CalendarHeader.tsx +++ b/src/components/features/calendarView/components/CalendarHeader.tsx @@ -13,17 +13,19 @@ import { showToast } from "@/lib/utils/toast"; interface CalendarHeaderProps { projectName: string; - projectStartedAt?: string; - projectEndedAt?: string; currentView: View; currentDate: Date; eventsCount: number; showHelp: boolean; + showMemo?: boolean; showFilter?: boolean; + projectStartedAt?: string; + projectEndedAt?: string; onToggleHelp: () => void; onToggleFilter?: () => void; onAddTask?: () => void; onProjectInfoClick?: () => void; + onToggleMemo?: () => void; } export default function CalendarHeader({ @@ -33,8 +35,10 @@ export default function CalendarHeader({ currentView, currentDate, eventsCount, + showMemo, showHelp, showFilter, + onToggleMemo, onToggleHelp, onToggleFilter, onAddTask, diff --git a/src/components/features/kanban/KanbanBoard.tsx b/src/components/features/kanban/KanbanBoard.tsx index f38260f..ce00baa 100644 --- a/src/components/features/kanban/KanbanBoard.tsx +++ b/src/components/features/kanban/KanbanBoard.tsx @@ -39,6 +39,8 @@ interface KanbanBoardProps { started_at?: string; ended_at?: string; } | null; + showMemoPanel?: boolean; + onToggleMemo?: () => void; onCreateTask?: ( taskData: Omit ) => void; @@ -59,6 +61,8 @@ const KanbanBoard = ({ onSelectTask, onTaskCreated, onProjectInfoClick, + showMemoPanel, + onToggleMemo, }: KanbanBoardProps) => { const projectId = project?.project_id || ""; const projectName = project?.project_name || "이름 없는 프로젝트"; @@ -333,6 +337,7 @@ const KanbanBoard = ({ onToggleFilter={() => setShowFilter(!showFilter)} onToggleHelp={() => setShowHelp(!showHelp)} showHelp={showHelp} + showMemo={showMemoPanel} hasActiveFilter={ filter.priority !== "all" || filter.assignee !== "all" || @@ -340,6 +345,7 @@ const KanbanBoard = ({ } searchQuery={searchQuery} onSearchChange={setSearchQuery} + onToggleMemo={onToggleMemo} tasksCount={tasks.length} project={project} onProjectInfoClick={onProjectInfoClick} diff --git a/src/components/features/kanban/MemoView.tsx b/src/components/features/kanban/MemoView.tsx index 4983199..c8301b5 100644 --- a/src/components/features/kanban/MemoView.tsx +++ b/src/components/features/kanban/MemoView.tsx @@ -53,9 +53,10 @@ type SortType = "newest" | "oldest"; interface MemoFormProps { projectId: string; + onClose?: () => void; } -const MemoView = ({ projectId }: MemoFormProps) => { +const MemoView = ({ projectId, onClose }: MemoFormProps) => { // === 기본 상태 관리 === const [memos, setMemos] = useState([]); // 서버에서 받은 원본 메모 목록 const [newMemo, setNewMemo] = useState(""); // 새 메모 입력 내용 @@ -524,6 +525,16 @@ const MemoView = ({ projectId }: MemoFormProps) => { }`} /> + {onClose && ( + + )}
diff --git a/src/components/features/kanban/components/KanbanHeader.tsx b/src/components/features/kanban/components/KanbanHeader.tsx index 2f41ac9..7b76861 100644 --- a/src/components/features/kanban/components/KanbanHeader.tsx +++ b/src/components/features/kanban/components/KanbanHeader.tsx @@ -4,39 +4,43 @@ import { useState, useRef, useEffect } from "react"; import { format } from "date-fns"; import { ko } from "date-fns/locale"; import { showToast } from "@/lib/utils/toast"; -import { Info, SlidersHorizontal, HelpCircle, Plus, Search, X } from "lucide-react"; +import { Info, SlidersHorizontal, HelpCircle, Plus, Search, X, StickyNote } from "lucide-react"; interface KanbanHeaderProps { projectName: string; - onAddClick: () => void; - onToggleFilter: () => void; - onToggleHelp: () => void; - showHelp: boolean; - hasActiveFilter?: boolean; - searchQuery?: string; - onSearchChange?: (q: string) => void; - tasksCount: number; project?: { project_id?: string; project_name: string; started_at?: string; ended_at?: string; } | null; + showHelp: boolean; + showMemo?: boolean; + tasksCount: number; + searchQuery?: string; + hasActiveFilter?: boolean; + onAddClick: () => void; + onToggleHelp: () => void; + onToggleMemo?: () => void; + onToggleFilter: () => void; onProjectInfoClick?: () => void; + onSearchChange?: (q: string) => void; } export default function KanbanHeader({ + project, projectName, + showHelp, + showMemo, + tasksCount, + searchQuery = "", + hasActiveFilter = false, onAddClick, onToggleFilter, onToggleHelp, - showHelp, - hasActiveFilter = false, - searchQuery = "", onSearchChange, - tasksCount, - project, onProjectInfoClick, + onToggleMemo, }: KanbanHeaderProps) { const [searchOpen, setSearchOpen] = useState(false); const searchRef = useRef(null); @@ -178,6 +182,18 @@ export default function KanbanHeader({ )} + {onToggleMemo && ( + + )} + + diff --git a/src/components/layout/BottomNavigation.tsx b/src/components/layout/BottomNavigation.tsx index 7f7ba4d..3ac4739 100644 --- a/src/components/layout/BottomNavigation.tsx +++ b/src/components/layout/BottomNavigation.tsx @@ -52,25 +52,7 @@ const BottomNavigation = ({ ), }, - { - id: "memo" as NavItem, - label: "메모", - icon: ( - - - - ), - }, + ]; return ( diff --git a/src/components/shared/Container.tsx b/src/components/shared/Container.tsx index 183c6f2..d63da06 100644 --- a/src/components/shared/Container.tsx +++ b/src/components/shared/Container.tsx @@ -9,7 +9,7 @@ export default function Container({ }: ContainerProps) { return (
{children} diff --git a/src/features/project/ui/ProjectCard.tsx b/src/features/project/ui/ProjectCard.tsx index c865707..95a0e71 100644 --- a/src/features/project/ui/ProjectCard.tsx +++ b/src/features/project/ui/ProjectCard.tsx @@ -1,5 +1,7 @@ "use client"; +import { useState } from "react"; + import { Icon } from "@/components/shared/Icon"; import { DeleteDialog } from "./DeleteDialog"; import { deleteProject, deleteProjectMember } from "../model"; @@ -8,7 +10,7 @@ import { showToast } from "@/lib/utils/toast"; import { useQueryClient } from "@tanstack/react-query"; import { queryKeys } from "@/lib/constants/queryKeys"; import type { Project, ProjectStatus } from "../model"; -import { Pencil } from "lucide-react"; +import { MoreVertical,Pencil } from "lucide-react"; interface ProjectCardProps { project: Project; @@ -38,6 +40,8 @@ export default function ProjectCard({ project, projectMember }: ProjectCardProps const dot = STATUS_DOT[project.status] ?? STATUS_DOT.active; const deadline = formatDeadline(project.ended_at); + const [isMenuOpen, setIsMenuOpen] = useState(false); + async function handleDelete() { await deleteProject(project.project_id); await deleteProjectMember(project.project_id); @@ -50,6 +54,42 @@ export default function ProjectCard({ project, projectMember }: ProjectCardProps onClick={() => router.push(`/project/workspace/${project.project_id}`)} className="group relative flex flex-col bg-white dark:bg-card rounded-[14px] border border-[#bde3ec] dark:border-border p-6 cursor-pointer transition-all duration-200 hover:shadow-md hover:-translate-y-0.5" > + {/* ⋮ 메뉴 버튼 — 항상 표시 */} +
e.stopPropagation()} +> + + + {isMenuOpen && ( + <> + {/* 외부 클릭 시 닫기 */} +
setIsMenuOpen(false)} + /> +
+ +
+ { setIsMenuOpen(false); handleDelete(); }} /> +
+
+ + )} +
+ {/* 타입 라벨 */}
@@ -66,7 +106,7 @@ export default function ProjectCard({ project, projectMember }: ProjectCardProps

- {/* 마감일 + 멤버 수 + 호버 액션 */} + {/* 마감일 + 멤버 수 */}
{deadline ?? "마감일 없음"} @@ -77,20 +117,7 @@ export default function ProjectCard({ project, projectMember }: ProjectCardProps {memberCount}명 -
e.stopPropagation()} - > - -
- -
-
+