diff --git a/src/app/(with-header-sidebar)/layout.module.css b/src/app/(with-header-sidebar)/layout.module.css index 1737a4c..12b5dae 100644 --- a/src/app/(with-header-sidebar)/layout.module.css +++ b/src/app/(with-header-sidebar)/layout.module.css @@ -5,7 +5,6 @@ .sideBarWrapper { border-right: 1px solid var(--gray-300); - padding: 22px; } .mainWrapper { diff --git a/src/app/(with-header-sidebar)/mydashboard/_hooks/useApi.ts b/src/app/(with-header-sidebar)/mydashboard/_hooks/useApi.ts index 54c9b8f..b392917 100644 --- a/src/app/(with-header-sidebar)/mydashboard/_hooks/useApi.ts +++ b/src/app/(with-header-sidebar)/mydashboard/_hooks/useApi.ts @@ -4,7 +4,7 @@ import axiosInstance from '../_utils/axiosInstance'; type UseApiFetchReturnType = { data: T | null; - loading: boolean; + isLoading: boolean; error: AxiosError | null; refetch: () => Promise; }; @@ -27,12 +27,12 @@ export default function useApi< options: RequestOptions ): UseApiFetchReturnType { const [data, setData] = useState(null); - const [loading, setLoading] = useState(true); + const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const fetchData = useCallback(async () => { // TODO if (loading) return; add? - setLoading(true); + setIsLoading(true); setError(null); const config = { @@ -48,7 +48,7 @@ export default function useApi< } catch (err) { setError(err as AxiosError); } finally { - setLoading(false); + setIsLoading(false); } }, [url, JSON.stringify(options)]); @@ -56,5 +56,5 @@ export default function useApi< fetchData(); }, [fetchData]); - return { data, loading, error, refetch: fetchData }; + return { data, isLoading, error, refetch: fetchData }; } diff --git a/src/app/(with-header-sidebar)/mydashboard/_utils/axiosInstance.ts b/src/app/(with-header-sidebar)/mydashboard/_utils/axiosInstance.ts index bbf84ce..858fb8c 100644 --- a/src/app/(with-header-sidebar)/mydashboard/_utils/axiosInstance.ts +++ b/src/app/(with-header-sidebar)/mydashboard/_utils/axiosInstance.ts @@ -13,6 +13,7 @@ axiosInstance.interceptors.request.use( (config) => { const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NDgwNCwidGVhbUlkIjoiMTAtMSIsImlhdCI6MTczMTcyMzkwNywiaXNzIjoic3AtdGFza2lmeSJ9.k8FqEAl7DbhwxhJNAkkMq8lYrgStN-9I3xrsR0cYm2c'; // TODO: Add token + // 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NDgwNywidGVhbUlkIjoiMTAtMSIsImlhdCI6MTczMTc2OTIwNywiaXNzIjoic3AtdGFza2lmeSJ9.yhISPAxnBlD28SkCY0mUxcIM5YuwAAib2k7j15fmlvA'; // TODO: Add token if (token) { config.headers['Authorization'] = `Bearer ${token}`; } diff --git a/src/app/reset.css b/src/app/reset.css index 55e1c49..6535c60 100644 --- a/src/app/reset.css +++ b/src/app/reset.css @@ -37,4 +37,6 @@ button { a { text-decoration: none; color: inherit; + display: inherit; + width: inherit; } diff --git a/src/components/header/Header.module.css b/src/components/header/Header.module.css index ea977d4..d801a97 100644 --- a/src/components/header/Header.module.css +++ b/src/components/header/Header.module.css @@ -27,8 +27,8 @@ align-items: center; gap: 8px; background-color: transparent; - border-radius: 6px; border: 1px solid var(--gray-300); + border-radius: 6px; color: var(--gray-500); font-size: 14px; font-weight: 500; @@ -55,6 +55,35 @@ align-items: center; } +.userInfoWrapper:hover { + cursor: pointer; +} + +.myMenu { + position: absolute; + top: 75px; + right: 10px; + padding: 6px 0; + border: 1px solid var(--gray-300); + border-radius: 6px; + color: var(--gray-500); + font-size: 14px; + font-weight: 500; + display: flex; + flex-direction: column; + gap: 5px; +} + +.myMenu div { + padding: 3px 12px; + transition: background-color 0.3s ease; +} + +.myMenu div:hover { + cursor: pointer; + background: var(--gray-200); +} + @media screen and (min-width: 768px) { .header { padding-left: 40px; @@ -75,17 +104,24 @@ font-size: 16px; } + .icon { + display: block; + } + .userInfoWrapper { margin-left: 36px; } + + .myMenu { + min-width: 120px; + text-align: center; + top: 80px; + right: 50px; + } } @media screen and (min-width: 1199px) { .header { padding-right: 80px; } - - .icon { - display: block; - } } diff --git a/src/components/header/Header.tsx b/src/components/header/Header.tsx index 74024a7..d82305a 100644 --- a/src/components/header/Header.tsx +++ b/src/components/header/Header.tsx @@ -1,9 +1,10 @@ 'use client'; -import { usePathname } from 'next/navigation'; +import { usePathname, useRouter } from 'next/navigation'; import Image from 'next/image'; import Button from '../Button'; import UserInfo from './UserInfo'; import styles from './Header.module.css'; +import { useState } from 'react'; interface HeaderProps { component?: React.ComponentType; @@ -11,6 +12,13 @@ interface HeaderProps { export default function Header({ component: Component }: HeaderProps) { const pathname = usePathname(); + const router = useRouter(); + + const [isMenuVisible, setIsMenuVisible] = useState(false); + + const handleUserInfoClick = () => { + setIsMenuVisible(!isMenuVisible); + }; return (
@@ -43,9 +51,16 @@ export default function Header({ component: Component }: HeaderProps) { )}
-
+
+ {isMenuVisible && ( +
+
router.push('/mydashboard')}>내 대시보드
+
router.push('/mypage')}>내 정보
+
router.push('/')}>로그아웃
+
+ )}
); diff --git a/src/components/header/UserInfo.module.css b/src/components/header/UserInfo.module.css index 19bd968..6a10415 100644 --- a/src/components/header/UserInfo.module.css +++ b/src/components/header/UserInfo.module.css @@ -22,4 +22,11 @@ color: var(--black-100); font-size: 16px; font-weight: 500; + display: none; +} + +@media screen and (min-width: 768px) { + .nickname { + display: inline; + } } diff --git a/src/components/header/UserInfo.tsx b/src/components/header/UserInfo.tsx index 311d008..30a00f7 100644 --- a/src/components/header/UserInfo.tsx +++ b/src/components/header/UserInfo.tsx @@ -1,6 +1,6 @@ 'use client'; -import type { User } from '@/app/(with-header-sidebar)/mydashboard/_types/user'; +import type { User } from '@/types/user'; import Image from 'next/image'; import useWindowSize from '@/app/(with-header-sidebar)/mydashboard/_hooks/useWindowSize'; import { useState, useEffect } from 'react'; @@ -11,9 +11,9 @@ const user: User = { id: 1, email: 'heejin@gmail.com', nickname: 'heejin', - // profileImageUrl: - // 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/taskify/profile_image/10-1_4804_1731757528194.jpeg', - profileImageUrl: null, + profileImageUrl: + 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/taskify/profile_image/10-1_4804_1731757528194.jpeg', + // profileImageUrl: null, createdAt: '2024-11-15T14:29:07.482Z', }; @@ -37,16 +37,16 @@ export default function UserInfo() { return ( <> - {profileImageUrl ? ( - 프로필 이미지 - ) : ( -
+
+ {profileImageUrl ? ( + 프로필 이미지 + ) : (
{email[0].toUpperCase()}
- {!isMobile && {nickname}} -
- )} + )} + {nickname} +
); } diff --git a/src/components/sidebar/Dashboards.module.css b/src/components/sidebar/Dashboards.module.css index ca35715..9a5290c 100644 --- a/src/components/sidebar/Dashboards.module.css +++ b/src/components/sidebar/Dashboards.module.css @@ -1,4 +1,5 @@ -.dashBoards { +.dashboards { + width: 100%; display: flex; flex-direction: column; justify-content: center; @@ -6,5 +7,88 @@ } .active { - color: salmon; + border-radius: 4px; + background: var(--violet-light); +} + +.titleContainer { + display: flex; + justify-content: center; + align-items: center; + padding: 16px; +} + +.dot { + width: 8px; + height: 8px; + border-radius: 999px; +} + +.title, +.crown { + display: none; + color: var(--gray-500); + font-size: 18px; + font-weight: 500; +} + +.arrowLeft, +.arrowRight { + border: 1px solid var(--gray-300); + border-right: none; + border-radius: 0; + background: var(--white); + height: 35px; +} + +.arrowRight { + border-top: none; +} + +.arrowLeft:disabled, +.arrowRight:disabled { + background: var(--white); +} + +@media screen and (min-width: 768px) { + .dashboardsWrapper { + margin-top: 10px; + display: flex; + flex-direction: column; + gap: 15px; + } + + .title, + .crown { + display: inline; + } + + .crown { + margin-left: -8px; + } + + .titleContainer { + justify-content: flex-start; + gap: 16px; + padding: 8px 10px; + } + + .arrowWrapper { + margin-top: 20px; + } + + .arrowLeft, + .arrowRight { + border: 1px solid var(--gray-300); + width: 40px; + height: 40px; + } + + .arrowLeft { + border-radius: 4px 0px 0px 4px; + } + + .arrowRight { + border-radius: 0px 4px 4px 0px; + } } diff --git a/src/components/sidebar/Dashboards.tsx b/src/components/sidebar/Dashboards.tsx index 7b099c3..c028efc 100644 --- a/src/components/sidebar/Dashboards.tsx +++ b/src/components/sidebar/Dashboards.tsx @@ -1,31 +1,121 @@ import Link from 'next/link'; import { usePathname } from 'next/navigation'; -import type { GetDashboardsResponse } from '@/app/(with-header-sidebar)/mydashboard/_types/dashboards'; -import styles from './Dashboards.module.css'; +import type { + Dashboard, + GetDashboardsResponse, +} from '@/app/(with-header-sidebar)/mydashboard/_types/dashboards'; import useApi from '@/app/(with-header-sidebar)/mydashboard/_hooks/useApi'; +import Image from 'next/image'; +import Button from '../Button'; +import { useState } from 'react'; +import styles from './Dashboards.module.css'; -export default function Dashboards() { - const pathname = usePathname(); +const PAGE_SIZE = 12; +export default function Dashboards() { + const [page, setPage] = useState(1); const { data } = useApi('/dashboards', { method: 'GET', - params: { navigationMethod: 'infiniteScroll', page: 1, size: 10 }, + params: { navigationMethod: 'pagination', page, size: PAGE_SIZE }, }); - const dashboards = data?.dashboards; + const dashboards = data?.dashboards ?? []; + const totalCount = data?.totalCount ?? 0; + const totalPages = Math.ceil(totalCount / PAGE_SIZE); + + const handlePageChange = (direction: 'next' | 'prev') => { + setPage((prevPage) => { + if (direction === 'next' && prevPage < totalPages) return prevPage + 1; + if (direction === 'prev' && prevPage > 1) return prevPage - 1; + return prevPage; + }); + }; return ( -
- {dashboards && - dashboards.map((board) => ( - - {board.title} - +
+
    + {dashboards.map((board) => ( + ))} +
+ +
+ ); +} + +function DashboardItem({ id, color, title, createdByMe }: Dashboard) { + const isActive = usePathname() === `/dashboard/${id}`; + + return ( +
  • + +
    +
    + {title} + {createdByMe && ( + + 왕관 + + )} +
    + +
  • + ); +} + +function Pagination({ + currentPage, + totalPages, + onPageChange, +}: { + currentPage: number; + totalPages: number; + onPageChange: (direction: 'next' | 'prev') => void; +}) { + const isFirstPage = currentPage === 1; + const isLastPage = currentPage >= totalPages; + + return ( +
    + +
    ); } diff --git a/src/components/sidebar/SideBar.module.css b/src/components/sidebar/SideBar.module.css index 7221a2e..5fa9178 100644 --- a/src/components/sidebar/SideBar.module.css +++ b/src/components/sidebar/SideBar.module.css @@ -2,19 +2,18 @@ display: flex; flex-direction: column; align-items: center; - gap: 30px; + gap: 15px; } .logo { background-color: transparent; - text-align: left; + padding-top: 22px; + margin-bottom: 10px; + text-align: center; } .addDashBoardsContainer { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; + padding: 0 22px; } .addDashBoardsTitle { @@ -29,6 +28,20 @@ @media screen and (min-width: 768px) { .sideBar { min-width: 155px; + padding: 0 12px; + } + + .logo { + text-align: left; + margin-bottom: 25px; + } + + .addDashBoardsContainer { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + padding: 0; } .addDashBoardsTitle { diff --git a/src/components/sidebar/SideBar.tsx b/src/components/sidebar/SideBar.tsx index f41be79..30afc5b 100644 --- a/src/components/sidebar/SideBar.tsx +++ b/src/components/sidebar/SideBar.tsx @@ -3,15 +3,21 @@ import Image from 'next/image'; import Button from '../Button'; import useWindowSize from '@/app/(with-header-sidebar)/mydashboard/_hooks/useWindowSize'; -import Dashboards from './DashBoards'; +import Dashboards from './Dashboards'; +import { useRouter } from 'next/navigation'; import styles from './SideBar.module.css'; export default function SideBar() { const { isMobile } = useWindowSize(); + const router = useRouter(); return (
    -