Skip to content
Merged
58 changes: 53 additions & 5 deletions src/app/(main)/goal/notes/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
'use client';

import React, { useState } from 'react';

import { useNoteSidebar } from '@/app/providers/NoteSidebarProvider';
import NotesClient from '@/components/notes/NotesClient';
import NoteSidebar from '@/components/ui/NoteSidebar/NoteSidebar';
import { TodoWithNotes } from '@/interfaces/todo';
import { cn } from '@/lib/utils';

interface NotesPageProps {
Expand All @@ -7,14 +14,55 @@ interface NotesPageProps {
}>;
}

const NotesPage = async ({ searchParams }: NotesPageProps) => {
const params = await searchParams;
const NotesPage = ({ searchParams }: NotesPageProps) => {
const params = React.use(searchParams);
const goalId = params.goalId ? Number(params.goalId) : undefined;

const { setIsNoteSidebarOpen } = useNoteSidebar(); // 사이드바 열림 여부
const [selectedTodo, setSelectedTodo] = useState<TodoWithNotes | null>(null);

const handleTodoClick = (todo: TodoWithNotes) => {
setSelectedTodo(todo);
setIsNoteSidebarOpen(true);
};

const handleCloseSidebar = () => {
setIsNoteSidebarOpen(false);
setSelectedTodo(null);
};

return (
<div className={cn('h-full w-full sm:mt-54 md:mt-0 lg:mt-0')}>
<NotesClient initialGoalId={goalId} />
</div>
<>
<div className={cn('h-full w-full sm:mt-54 md:mt-0 lg:mt-0')}>
<NotesClient initialGoalId={goalId} onTodoClick={handleTodoClick} />
</div>

{/* 데스크탑 사이드바 */}
{selectedTodo && (
<div className="hidden lg:block">
<NoteSidebar
isOpen={true}
todo={selectedTodo}
goalTitle={selectedTodo.goalTitle || '목표 없음'}
onClose={handleCloseSidebar}
isDesktop={true}
/>
</div>
)}

{/* 모바일/태블릿 사이드바 */}
{selectedTodo && (
<div className="lg:hidden">
<NoteSidebar
isOpen={true}
todo={selectedTodo}
goalTitle={selectedTodo.goalTitle || '목표 없음'}
onClose={handleCloseSidebar}
isDesktop={false}
/>
</div>
)}
</>
);
};

Expand Down
15 changes: 13 additions & 2 deletions src/app/(main)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

import { usePathname } from 'next/navigation';

import { NoteSidebarProvider, useNoteSidebar } from '@/app/providers/NoteSidebarProvider';
import { SidebarProvider } from '@/app/providers/SidebarProvider';
import Sidebar from '@/components/sidebar/Sidebar';

export default function MainLayout({ children }: { children: React.ReactNode }) {
return (
<SidebarProvider>
<SidebarLayout>{children}</SidebarLayout>
<NoteSidebarProvider>
<SidebarLayout>{children}</SidebarLayout>
</NoteSidebarProvider>
</SidebarProvider>
);
}
Expand All @@ -29,10 +32,18 @@ function SidebarLayout({ children }: { children: React.ReactNode }) {
}

function MainContent({ children }: { children: React.ReactNode }) {
const { isNoteSidebarOpen } = useNoteSidebar();

return (
<main className="flex min-w-0 flex-1 flex-col overflow-y-auto transition-all duration-300">
<div className="flex w-full flex-1 justify-center sm:px-16 md:pr-13 md:pl-93 lg:px-30">
<div className="w-full max-w-1296 sm:py-16 md:py-36">{children}</div>
<div
className={`w-full max-w-1296 transition-all duration-300 sm:py-16 md:py-36 ${
isNoteSidebarOpen ? 'lg:mr-360' : ''
}`}
>
{children}
</div>
</div>
</main>
);
Expand Down
2 changes: 1 addition & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo

return (
<html lang="ko" className={pretendard.variable}>
<body className="bg-background min-h-screen">
<body className="min-h-screen">
<AuthProvider initialToken={token}>
<MswProvider>
<ReactQueryProvider>{children}</ReactQueryProvider>
Expand Down
28 changes: 28 additions & 0 deletions src/app/providers/NoteSidebarProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use client';

import { createContext, ReactNode, useContext, useState } from 'react';

interface NoteSidebarContextType {
isNoteSidebarOpen: boolean;
setIsNoteSidebarOpen: (value: boolean) => void;
}

const NoteSidebarContext = createContext<NoteSidebarContextType | undefined>(undefined);

export const useNoteSidebar = () => {
const context = useContext(NoteSidebarContext);
if (!context) {
throw new Error('useNoteSidebar must be used within a NoteSidebarProvider');
}
return context;
};

export const NoteSidebarProvider = ({ children }: { children: ReactNode }) => {
const [isNoteSidebarOpen, setIsNoteSidebarOpen] = useState(false);

return (
<NoteSidebarContext.Provider value={{ isNoteSidebarOpen, setIsNoteSidebarOpen }}>
{children}
</NoteSidebarContext.Provider>
);
};
2 changes: 1 addition & 1 deletion src/components/goals/goalDetail/GoalDetailClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const GoalDetailClient = ({ goalId }: GoalDetailClientProps) => {
}

return (
<div className="h-full w-full overflow-hidden lg:max-w-1184">
<div className="h-full w-full overflow-hidden lg:max-w-1296">
<div className="mx-auto flex h-full w-full flex-col p-6">
{/* 목표 정보 헤더 */}
<div className="mb-24 flex flex-shrink-0 items-center gap-8">
Expand Down
71 changes: 22 additions & 49 deletions src/components/notes/NotesClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { useMemo, useState } from 'react';

import TodoWithNoteList from '@/components/notes/TodoWithNoteList';
import GoalSelector from '@/components/todos/GoalSelector';
import NoteSidebar from '@/components/ui/NoteSidebar/NoteSidebar';
import Pagination from '@/components/ui/Pagination';
import { useTodosWithNotes } from '@/hooks/useNotes';
import { TodoWithNotes } from '@/interfaces/todo';
Expand All @@ -13,19 +12,17 @@ const ITEMS_PER_PAGE = 6; // 페이지당 표시할 할 일 개수

interface NotesClientProps {
initialGoalId?: number;
onTodoClick: (todo: TodoWithNotes) => void;
}

const NotesClient = ({ initialGoalId }: NotesClientProps) => {
const NotesClient = ({ initialGoalId, onTodoClick }: NotesClientProps) => {
const [selectedGoalId, setSelectedGoalId] = useState<number>(initialGoalId || 0);
const [isSidebarOpen, setIsSidebarOpen] = useState<boolean>(false);
const [selectedTodo, setSelectedTodo] = useState<TodoWithNotes | null>(null);
const [currentPage, setCurrentPage] = useState<number>(1);

const { data: allTodosWithNotes = [] } = useTodosWithNotes(
selectedGoalId === 0 ? undefined : selectedGoalId,
);

// 페이지네이션 계산
const paginatedData = useMemo(() => {
const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
const endIndex = startIndex + ITEMS_PER_PAGE;
Expand All @@ -42,28 +39,17 @@ const NotesClient = ({ initialGoalId }: NotesClientProps) => {
};
}, [allTodosWithNotes.length, currentPage]);

const handleTodoClick = (todo: TodoWithNotes) => {
setSelectedTodo(todo);
setIsSidebarOpen(true);
};

const handleCloseSidebar = () => {
setIsSidebarOpen(false);
setSelectedTodo(null);
};

const handlePageChange = (page: number) => {
setCurrentPage(page);
};

// 목표 변경 시 페이지 초기화
const handleGoalChange = (goalId: number) => {
setSelectedGoalId(goalId);
setCurrentPage(1);
};

return (
<div className="mx-auto h-full max-w-1184">
<div className="h-full w-full">
<header className="mb-32 sm:mb-44">
<div className="flex items-center">
<h1 className="text-text-01 text-display-24 sm:text-display-32 font-bold">
Expand All @@ -72,38 +58,25 @@ const NotesClient = ({ initialGoalId }: NotesClientProps) => {
</div>
</header>

<div className="flex">
<main className="flex-1">
<div className="mb-32">
<GoalSelector
selectedGoalId={selectedGoalId}
onSelectGoal={handleGoalChange}
variant="notes"
/>
</div>
<TodoWithNoteList todos={paginatedData} onTodoClick={handleTodoClick} />

{/* 페이지네이션 */}
{paginationInfo.totalPages > 1 && (
<Pagination
pagination={paginationInfo}
onPageChange={handlePageChange}
size="md"
maxVisiblePages={5}
/>
)}
</main>
</div>

{/* 할 일 클릭 시 열리는 사이드바 */}
{isSidebarOpen && selectedTodo && (
<NoteSidebar
isOpen={isSidebarOpen}
todo={selectedTodo}
goalTitle={selectedTodo.goalTitle || '목표 없음'}
onClose={handleCloseSidebar}
/>
)}
<main>
<div className="mb-32 w-full">
<GoalSelector
selectedGoalId={selectedGoalId}
onSelectGoal={handleGoalChange}
variant="notes"
/>
</div>
<TodoWithNoteList todos={paginatedData} onTodoClick={onTodoClick} />

{paginationInfo.totalPages > 1 && (
<Pagination
pagination={paginationInfo}
onPageChange={handlePageChange}
size="md"
maxVisiblePages={5}
/>
)}
</main>
</div>
);
};
Expand Down
2 changes: 2 additions & 0 deletions src/components/todos/GoalSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ const GoalSelector = ({
size="todo"
animation="slide"
shadow="md"
matchTriggerWidth
offsetPx={2}
className="max-h-200 w-full overflow-y-auto"
>
<div className="py-4">
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const buttonVariants = cva('flex cursor-pointer items-center justify-center', {
schedule: 'h-48 w-120 sm:w-165.5',
scheduleDashboard: 'h-40 w-120 md:w-160',
tempNote: 'h-40 w-84',
sideNote: 'h-48 w-260',
sideNote: 'h-40 w-100 md:h-48 md:w-200 lg:w-260',
error: 'h-44 w-200',
eula: 'h-48 w-520',
},
Expand Down
7 changes: 5 additions & 2 deletions src/components/ui/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const dropdownVariants = cva('rounded-b-[20px] border shadow-lg', {
lg: 'max-w-md min-w-64',
auto: 'w-auto max-w-xs',
goalListFilter: 'max-w-148',
todo: 'max-w-520',
todo: 'max-w-1296',
full: 'w-auto',
},
animation: {
Expand Down Expand Up @@ -56,6 +56,7 @@ export interface DropdownMenuProps extends VariantProps<typeof dropdownVariants>
position?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
matchTriggerWidth?: boolean;
zIndex?: number;
offsetPx?: number;
}

const DropdownMenu = ({
Expand All @@ -70,6 +71,7 @@ const DropdownMenu = ({
position = 'bottom-start',
matchTriggerWidth = false,
zIndex = 9999,
offsetPx,
}: DropdownMenuProps) => {
// Floating UI 설정
const { refs, floatingStyles, context } = useFloating({
Expand All @@ -78,7 +80,7 @@ const DropdownMenu = ({
if (!open) onClose();
},
middleware: [
offset(16), // 16px 간격
offset(offsetPx),
flip(), // 화면 경계에 닿으면 위치 자동 조정
shift(), // 화면 밖으로 나가지 않도록 이동
...(matchTriggerWidth
Expand All @@ -87,6 +89,7 @@ const DropdownMenu = ({
apply({ rects, elements }) {
Object.assign(elements.floating.style, {
width: `${rects.reference.width}px`,
boxSizing: 'border-box',
});
},
}),
Expand Down
3 changes: 2 additions & 1 deletion src/components/ui/NoteSidebar/NoteDetailReadMode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ const NoteDetailReadMode = ({ note, onEdit, goalTitle, todoTitle }: NoteDetailRe
className="bg-primary-01 text-white"
icon={<PencilIcon className="mr-2" />}
>
수정하기
<span className="hidden sm:inline md:hidden">수정</span>
<span className="hidden md:inline">수정하기</span>
</Button>
</div>
</div>
Expand Down
Loading