From 1f25ce90c05dcb23df3c227db8bfc575a3dc2fe5 Mon Sep 17 00:00:00 2001 From: geha Date: Sun, 7 Sep 2025 13:29:37 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20=EA=B3=B5=ED=86=B5=20fetching=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20useAsync=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eslint.config.ts | 1 + src/components/ui/dashboard-header/index.tsx | 8 +- src/hooks/useAsync.ts | 83 ++++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 src/hooks/useAsync.ts diff --git a/eslint.config.ts b/eslint.config.ts index 03a898c..af5eecc 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -52,5 +52,6 @@ export default tseslint.config(sheriff(sheriffOptions), { '@typescript-eslint/explicit-module-boundary-types': 'off', 'fsecond/prefer-destructured-optionals': 'off', '@typescript-eslint/no-misused-spread': 'off', + '@typescript-eslint/prefer-nullish-coalescing': 'off', }, }); diff --git a/src/components/ui/dashboard-header/index.tsx b/src/components/ui/dashboard-header/index.tsx index 296a0bc..06d9992 100644 --- a/src/components/ui/dashboard-header/index.tsx +++ b/src/components/ui/dashboard-header/index.tsx @@ -4,11 +4,17 @@ import { type ReactNode, useState } from 'react'; import HeaderDropdown from '@/components/ui/dashboard-header/header-dropdown'; import InviteMemberModal from '@/components/ui/dashboard-header/invite-member-modal'; import ProfileList from '@/components/ui/dashboard-header/profile-list'; +import { useFetch } from '@/hooks/useAsync'; +import { getMemberList } from '@/lib/members/api'; const buttonClass = 'flex-center border-gray-3 text-md mobile:px-3 mobile:py-1.5 h-9 cursor-pointer gap-2 rounded-lg border-1 px-4 py-2.5 hover:bg-gray-4 active:bg-gray-3'; export default function DashboardHeader(): ReactNode { + const { data, loading, error, refetch } = useFetch({ + asyncFunction: () => getMemberList({ dashboardId: 1 }), + deps: [], + }); const [isModalOpen, setIsModalOpen] = useState(false); const dashboardId = 1; @@ -25,7 +31,7 @@ export default function DashboardHeader(): ReactNode { }; return ( -
+

내 대시보드

{ + asyncFunction: () => Promise; + deps: unknown[]; + immediate?: boolean; +} +interface fetchStateInterface { + loading: boolean; + data: T | null; + error: Error | null; +} +export default function useAsync({ + asyncFunction, + deps = [], + immediate = false, +}: useFetchParams) { + const [state, setState] = useState>({ + data: null, + loading: false, + error: null, + }); + + const asyncFnRef = useRef(asyncFunction); + + useEffect(() => { + asyncFnRef.current = asyncFunction; + }, [asyncFunction]); + + const refetchRef = useRef<() => Promise | null>(null); + + if (refetchRef.current === null) { + refetchRef.current = async () => { + setState((prev) => ({ ...prev, error: null, loading: true })); + try { + const response = await asyncFnRef.current(); + + setState((prev) => ({ ...prev, loading: false, data: response })); + } catch (error) { + if (error instanceof Error) { + setState({ data: null, loading: false, error }); + } + } + }; + } + + useEffect(() => { + if (refetchRef.current === null) { + return; + } + if (immediate) { + refetchRef.current(); + } + }, [immediate, ...deps]); + + return { ...state, refetch: refetchRef }; +} + +export function useFetch({ asyncFunction, deps = [] }: useFetchParams) { + return useAsync({ + asyncFunction, + immediate: true, + deps, + }); +} + +export function useMutation({ + asyncFunction, + deps = [], +}: useFetchParams) { + const { + data, + loading, + error, + refetch: mutate, + } = useAsync({ + asyncFunction, + immediate: false, + deps, + }); + + return { data, loading, error, mutate }; +} From 66d3b61e2ef3dbc7c1a915eacb0d396d7a4d3a7d Mon Sep 17 00:00:00 2001 From: geha Date: Sun, 7 Sep 2025 15:01:04 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20dashboard=20header=20profile-list?= =?UTF-8?q?=20API=20=EC=97=B0=EB=8F=99=20+=20zodSchema=20type=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ui/dashboard-header/index.tsx | 4 -- .../ui/dashboard-header/profile-list.tsx | 53 ++++++++++++++----- src/hooks/useAsync.ts | 8 ++- src/lib/cards/type.ts | 2 +- src/lib/comments/type.ts | 2 +- src/lib/custom-fetch.ts | 1 + src/lib/members/type.ts | 2 +- src/lib/users/type.ts | 2 +- 8 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/components/ui/dashboard-header/index.tsx b/src/components/ui/dashboard-header/index.tsx index 06d9992..80f6936 100644 --- a/src/components/ui/dashboard-header/index.tsx +++ b/src/components/ui/dashboard-header/index.tsx @@ -11,10 +11,6 @@ const buttonClass = 'flex-center border-gray-3 text-md mobile:px-3 mobile:py-1.5 h-9 cursor-pointer gap-2 rounded-lg border-1 px-4 py-2.5 hover:bg-gray-4 active:bg-gray-3'; export default function DashboardHeader(): ReactNode { - const { data, loading, error, refetch } = useFetch({ - asyncFunction: () => getMemberList({ dashboardId: 1 }), - deps: [], - }); const [isModalOpen, setIsModalOpen] = useState(false); const dashboardId = 1; diff --git a/src/components/ui/dashboard-header/profile-list.tsx b/src/components/ui/dashboard-header/profile-list.tsx index 9a07729..440c95a 100644 --- a/src/components/ui/dashboard-header/profile-list.tsx +++ b/src/components/ui/dashboard-header/profile-list.tsx @@ -1,21 +1,50 @@ +import { useRouter } from 'next/router'; import type { ReactNode } from 'react'; import ChipProfile from '@/components/ui/chip/chip-profile'; +import { useFetch } from '@/hooks/useAsync'; +import { getMemberList } from '@/lib/members/api'; +import { getStringFromQuery } from '@/utils/getContextQuery'; export default function ProfileList(): ReactNode { + const router = useRouter().query; + const dashboardId = getStringFromQuery(router, 'dashboardId'); + const { data, loading, error } = useFetch({ + asyncFunction: () => getMemberList({ dashboardId: Number(dashboardId) }), + deps: [dashboardId], + }); + const maxDisplayLength = { + desktop: 4, + tablet: 2, + }; + + console.log(dashboardId, data); + if (!data || error) { + return null; + } + const excessNumber = data.totalCount - maxDisplayLength.desktop; + return (
    - {Array(2) - .fill('0') - .map((num: number) => { - return ( -
  • - -
  • - ); - })} -
  • - -
  • + {data.members.slice(0, maxDisplayLength.desktop + 1).map((member) => { + return ( +
  • + +
  • + ); + })} + {excessNumber > 0 && ( +
  • + +
  • + )}
); } diff --git a/src/hooks/useAsync.ts b/src/hooks/useAsync.ts index 2087bfa..5e587c4 100644 --- a/src/hooks/useAsync.ts +++ b/src/hooks/useAsync.ts @@ -56,10 +56,14 @@ export default function useAsync({ return { ...state, refetch: refetchRef }; } -export function useFetch({ asyncFunction, deps = [] }: useFetchParams) { +export function useFetch({ + asyncFunction, + deps = [], + immediate = true, +}: useFetchParams) { return useAsync({ asyncFunction, - immediate: true, + immediate, deps, }); } diff --git a/src/lib/cards/type.ts b/src/lib/cards/type.ts index ed98509..04ed686 100644 --- a/src/lib/cards/type.ts +++ b/src/lib/cards/type.ts @@ -7,7 +7,7 @@ export const cardSchema = z.object({ tags: z.array(z.string()), dueDate: z.string(), assignee: z.object({ - profileImageUrl: z.string(), + profileImageUrl: z.union([z.string(), z.null()]), nickname: z.string(), id: z.number(), }), diff --git a/src/lib/comments/type.ts b/src/lib/comments/type.ts index 97d49aa..8378a93 100644 --- a/src/lib/comments/type.ts +++ b/src/lib/comments/type.ts @@ -7,7 +7,7 @@ export const commentSchema = z.object({ updatedAt: z.string(), cardId: z.number(), author: z.object({ - profileImageUrl: z.string(), + profileImageUrl: z.union([z.string(), z.null()]), nickname: z.string(), id: z.number(), }), diff --git a/src/lib/custom-fetch.ts b/src/lib/custom-fetch.ts index c967894..6e2ac9f 100644 --- a/src/lib/custom-fetch.ts +++ b/src/lib/custom-fetch.ts @@ -41,6 +41,7 @@ export default async function customFetch( } const data = await res.json(); + console.log(data); try { return schema.parse(data); } catch (error) { diff --git a/src/lib/members/type.ts b/src/lib/members/type.ts index 0f0748e..2fb9793 100644 --- a/src/lib/members/type.ts +++ b/src/lib/members/type.ts @@ -7,7 +7,7 @@ export const memberListSchema = z.object({ userId: z.number(), email: z.string(), nickname: z.string(), - profileImageUrl: z.string(), + profileImageUrl: z.union([z.string(), z.null()]), createdAt: z.string(), updatedAt: z.string(), isOwner: z.boolean(), diff --git a/src/lib/users/type.ts b/src/lib/users/type.ts index ae43c5d..b15e2d9 100644 --- a/src/lib/users/type.ts +++ b/src/lib/users/type.ts @@ -28,7 +28,7 @@ export const updateMyInfoSchema = z.object({ }); export const uploadProfileImageSchema = z.object({ - profileImageUrl: z.string(), + profileImageUrl: z.union([z.string(), z.null()]), }); export type UserType = z.infer; From 3618d4100cf6cdb8a730986449f1c5702a1b81b9 Mon Sep 17 00:00:00 2001 From: geha Date: Sun, 7 Sep 2025 15:34:33 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20dashboard=20header=20member=20profi?= =?UTF-8?q?le-list=20=ED=91=9C=EC=8B=9C=20=EA=B8=B0=EB=8A=A5=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...opdown.tsx => header-profile-dropdown.tsx} | 23 ++++++-------- src/components/ui/dashboard-header/index.tsx | 26 ++++++++++------ .../ui/dashboard-header/profile-list.tsx | 28 +++++++++-------- src/hooks/useAsync.ts | 7 ++--- src/hooks/useIsBreakPoint.ts | 30 +++++++++++++++++++ src/lib/custom-fetch.ts | 1 - 6 files changed, 74 insertions(+), 41 deletions(-) rename src/components/ui/dashboard-header/{header-dropdown.tsx => header-profile-dropdown.tsx} (80%) create mode 100644 src/hooks/useIsBreakPoint.ts diff --git a/src/components/ui/dashboard-header/header-dropdown.tsx b/src/components/ui/dashboard-header/header-profile-dropdown.tsx similarity index 80% rename from src/components/ui/dashboard-header/header-dropdown.tsx rename to src/components/ui/dashboard-header/header-profile-dropdown.tsx index cbd1727..a36cad7 100644 --- a/src/components/ui/dashboard-header/header-dropdown.tsx +++ b/src/components/ui/dashboard-header/header-profile-dropdown.tsx @@ -1,20 +1,15 @@ import { useRouter } from 'next/router'; import { type ReactNode, useCallback, useEffect } from 'react'; -import ChipProfile, { - type ChipProfileProps, -} from '@/components/ui/chip/chip-profile'; +import ChipProfile from '@/components/ui/chip/chip-profile'; import Dropdown from '@/components/ui/dropdown'; -interface HeaderDropdownProps { - nickname: string; - profileLabel: string; - profileColor: ChipProfileProps['color']; -} -export default function HeaderDropdown({ - nickname, - profileLabel, - profileColor, -}: HeaderDropdownProps): ReactNode { +export default function HeaderProfileDropdwon({ + myNickname, +}: { + myNickname: string; +}): ReactNode { + const profileColor = 'yellow'; + const profileLabel = myNickname.slice(0, 1); const router = useRouter(); const handleMyPageButton = useCallback(() => { @@ -46,7 +41,7 @@ export default function HeaderDropdown({
- {nickname} + {myNickname}
getMyInfo(), + }); const [isModalOpen, setIsModalOpen] = useState(false); - const dashboardId = 1; + const router = useRouter().query; + const dashboardId = getStringFromQuery(router, 'dashboardId'); const handleOpenModal = () => { setIsModalOpen(true); @@ -58,12 +68,10 @@ export default function DashboardHeader(): ReactNode {
- - + {dashboardId && myInfo && ( + + )} + {myInfo && }
getMemberList({ dashboardId: Number(dashboardId) }), deps: [dashboardId], }); - const maxDisplayLength = { - desktop: 4, - tablet: 2, - }; + const isTablet = useIsBreakPoint(80); - console.log(dashboardId, data); if (!data || error) { return null; } - const excessNumber = data.totalCount - maxDisplayLength.desktop; + const maxDisplayLength = isTablet ? 2 : 4; + const excessNumber = data.totalCount - maxDisplayLength; return (
    - {data.members.slice(0, maxDisplayLength.desktop + 1).map((member) => { + {data.members.slice(0, maxDisplayLength).map((member) => { + if (member.userId === myId) { + return; + } + return (
  • { asyncFunction: () => Promise; - deps: unknown[]; + deps?: unknown[]; immediate?: boolean; } interface fetchStateInterface { @@ -68,10 +68,7 @@ export function useFetch({ }); } -export function useMutation({ - asyncFunction, - deps = [], -}: useFetchParams) { +export function useMutate({ asyncFunction, deps = [] }: useFetchParams) { const { data, loading, diff --git a/src/hooks/useIsBreakPoint.ts b/src/hooks/useIsBreakPoint.ts new file mode 100644 index 0000000..3744206 --- /dev/null +++ b/src/hooks/useIsBreakPoint.ts @@ -0,0 +1,30 @@ +import { useEffect, useState } from 'react'; +/** + * 화면 사이즈가 지정한 breakpoint(rem 기준) 아래로 내려가면, isBreakPoint 상태가 변화하는 hook + * + * // @custom-variant tablet (@media (max-width: 80rem)); + * // @custom-variant mobile (@media (max-width: 48rem)); + */ +const useIsBreakPoint = (breakpoint = 48) => { + const [isBreakPoint, setIsBreakPoint] = useState(false); + + useEffect(() => { + const media = window.matchMedia(`(max-width: ${String(breakpoint)}rem)`); + + setIsBreakPoint(media.matches); + + const handleMediaChange = () => { + setIsBreakPoint(media.matches); + }; + + media.addEventListener('change', handleMediaChange); + + return () => { + media.removeEventListener('change', handleMediaChange); + }; + }, [breakpoint]); + + return isBreakPoint; +}; + +export default useIsBreakPoint; diff --git a/src/lib/custom-fetch.ts b/src/lib/custom-fetch.ts index 6e2ac9f..c967894 100644 --- a/src/lib/custom-fetch.ts +++ b/src/lib/custom-fetch.ts @@ -41,7 +41,6 @@ export default async function customFetch( } const data = await res.json(); - console.log(data); try { return schema.parse(data); } catch (error) { From 1b76c4c326d0680ab8bd0e5b1dca81678bc4d42c Mon Sep 17 00:00:00 2001 From: geha Date: Sun, 7 Sep 2025 21:45:13 +0900 Subject: [PATCH 4/7] =?UTF-8?q?feat:=20side-menu=20API=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99=20+=20zodSchema=20type=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ui/dashboard-header/index.tsx | 42 ++++++---- .../ui/side-menu/dashboard-list.tsx | 80 +++++++++++-------- src/components/ui/side-menu/dot.tsx | 13 +++ src/components/ui/side-menu/index.tsx | 30 ++++++- src/hooks/useAsync.ts | 2 +- src/lib/cards/type.ts | 2 +- src/lib/comments/type.ts | 2 +- src/lib/dashboards/type.ts | 2 +- src/lib/invitations/type.ts | 2 +- src/utils/dashboard-color.ts | 20 +++++ 10 files changed, 140 insertions(+), 55 deletions(-) create mode 100644 src/components/ui/side-menu/dot.tsx create mode 100644 src/utils/dashboard-color.ts diff --git a/src/components/ui/dashboard-header/index.tsx b/src/components/ui/dashboard-header/index.tsx index 1c85faa..55b6522 100644 --- a/src/components/ui/dashboard-header/index.tsx +++ b/src/components/ui/dashboard-header/index.tsx @@ -1,11 +1,12 @@ import Image from 'next/image'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import { type ReactNode, useState } from 'react'; +import { type ReactNode, useEffect, useState } from 'react'; import HeaderProfileDropdwon from '@/components/ui/dashboard-header/header-profile-dropdown'; import InviteMemberModal from '@/components/ui/dashboard-header/invite-member-modal'; import ProfileList from '@/components/ui/dashboard-header/profile-list'; import { useFetch } from '@/hooks/useAsync'; +import { getDashBoard } from '@/lib/dashboards/api'; import { getMyInfo } from '@/lib/users/api'; import { getStringFromQuery } from '@/utils/getContextQuery'; @@ -13,17 +14,26 @@ const buttonClass = 'flex-center border-gray-3 text-md mobile:px-3 mobile:py-1.5 h-9 cursor-pointer gap-2 rounded-lg border-1 px-4 py-2.5 hover:bg-gray-4 active:bg-gray-3'; export default function DashboardHeader(): ReactNode { - const { - data: myInfo, - loading: myInfoLoading, - error: myInfoError, - } = useFetch({ - asyncFunction: () => getMyInfo(), - }); const [isModalOpen, setIsModalOpen] = useState(false); const router = useRouter().query; const dashboardId = getStringFromQuery(router, 'dashboardId'); + const { data: myInfo } = useFetch({ + asyncFunction: () => getMyInfo(), + }); + const { data: dashboardData, refetch } = useFetch({ + asyncFunction: () => getDashBoard(Number(dashboardId)), + deps: [dashboardId], + immediate: false, + }); + const isMyDashboard = dashboardId && dashboardData?.createdByMe; + + useEffect(() => { + if (dashboardId) { + refetch(); + } + }, [dashboardId, refetch]); + const handleOpenModal = () => { setIsModalOpen(true); }; @@ -40,13 +50,15 @@ export default function DashboardHeader(): ReactNode {

    내 대시보드

    - 왕관: 내 대시보드 아이콘 + {isMyDashboard && ( + 왕관: 내 대시보드 아이콘 + )}
  • + ); + })}
); } +const hexToClassName = { + '#7AC555': '*:fill-green', + '#760DDE': '*:fill-purple', + '#FFA500': '*:fill-orange', + '#76A5EA': '*:fill-blue', + '#E876EA': '*:fill-pink', +}; diff --git a/src/components/ui/side-menu/dot.tsx b/src/components/ui/side-menu/dot.tsx new file mode 100644 index 0000000..2a725b0 --- /dev/null +++ b/src/components/ui/side-menu/dot.tsx @@ -0,0 +1,13 @@ +export default function Dot() { + return ( + + + + ); +} diff --git a/src/components/ui/side-menu/index.tsx b/src/components/ui/side-menu/index.tsx index c336819..ad5c546 100644 --- a/src/components/ui/side-menu/index.tsx +++ b/src/components/ui/side-menu/index.tsx @@ -1,10 +1,32 @@ import Image from 'next/image'; import Link from 'next/link'; -import type { ReactNode } from 'react'; +import { type ReactNode, useState } from 'react'; import ButtonPagination from '@/components/ui/button/button-pagination'; import DashboardList from '@/components/ui/side-menu/dashboard-list'; +import { useFetch } from '@/hooks/useAsync'; +import { getDashBoardList } from '@/lib/dashboards/api'; +import { getMyInfo } from '@/lib/users/api'; export default function SideMenu(): ReactNode { + const [page, setPage] = useState(1); + const { data: myInfo } = useFetch({ + asyncFunction: () => getMyInfo(), + }); + const { + data: dashboardListData, + loading, + error, + } = useFetch({ + asyncFunction: () => { + return getDashBoardList({ + navigationMethod: 'pagination', + cursorId: 0, + page, + size: 10, + }); + }, + deps: [page], + }); const handleClickPrev = () => { console.log('click'); }; @@ -12,6 +34,10 @@ export default function SideMenu(): ReactNode { console.log('click'); }; + if (!dashboardListData || error) { + return null; + } + return (
- +
({ } }, [immediate, ...deps]); - return { ...state, refetch: refetchRef }; + return { ...state, refetch: refetchRef.current }; } export function useFetch({ diff --git a/src/lib/cards/type.ts b/src/lib/cards/type.ts index 04ed686..7317b51 100644 --- a/src/lib/cards/type.ts +++ b/src/lib/cards/type.ts @@ -18,7 +18,7 @@ export const cardSchema = z.object({ updatedAt: z.string(), }); export const cardListSchema = z.object({ - cursorId: z.number(), + cursorId: z.union([z.number(), z.null()]), totalCount: z.number(), cards: z.array(cardSchema), }); diff --git a/src/lib/comments/type.ts b/src/lib/comments/type.ts index 8378a93..900d040 100644 --- a/src/lib/comments/type.ts +++ b/src/lib/comments/type.ts @@ -13,7 +13,7 @@ export const commentSchema = z.object({ }), }); export const commentListSchema = z.object({ - cursorId: z.number(), + cursorId: z.union([z.number(), z.null()]), comments: z.array(commentSchema), }); export const deleteSchema = z.object(); diff --git a/src/lib/dashboards/type.ts b/src/lib/dashboards/type.ts index 3342fcd..19e9ba5 100644 --- a/src/lib/dashboards/type.ts +++ b/src/lib/dashboards/type.ts @@ -8,7 +8,7 @@ export const dashboardSchema = z.object({ createdByMe: z.boolean(), }); export const dashboardListSchema = z.object({ - cursorId: z.number(), + cursorId: z.union([z.number(), z.null()]), totalCount: z.number(), dashboards: z.array(dashboardSchema), }); diff --git a/src/lib/invitations/type.ts b/src/lib/invitations/type.ts index c858e25..2431242 100644 --- a/src/lib/invitations/type.ts +++ b/src/lib/invitations/type.ts @@ -23,7 +23,7 @@ export const invitationSchema = z.object({ }); export const invitationListSchema = z.object({ - cursorId: z.number(), + cursorId: z.union([z.number(), z.null()]), invitations: z.array(invitationSchema), }); export type InvitationType = z.infer; diff --git a/src/utils/dashboard-color.ts b/src/utils/dashboard-color.ts new file mode 100644 index 0000000..690e23f --- /dev/null +++ b/src/utils/dashboard-color.ts @@ -0,0 +1,20 @@ +export type dashboardColorType = + | '#7AC555' + | '#760DDE' + | '#FFA500' + | '#76A5EA' + | '#E876EA'; +export const colorToHex = { + green: '#7AC555', + purple: '#760DDE', + orange: '#FFA500', + blue: '#76A5EA', + pink: '#E876EA', +}; +export const hexToColor = { + '#7AC555': 'green', + '#760DDE': 'purple', + '#FFA500': 'orange', + '#76A5EA': 'blue', + '#E876EA': 'pink', +}; From 7f3f23ab09fa2777f025416942667d71932f9832 Mon Sep 17 00:00:00 2001 From: geha Date: Sun, 7 Sep 2025 22:16:16 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat:=20dashboard-header=20title=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ui/dashboard-header/index.tsx | 23 ++++++++++++++++--- .../ui/side-menu/dashboard-list.tsx | 16 +++++++++---- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/components/ui/dashboard-header/index.tsx b/src/components/ui/dashboard-header/index.tsx index 55b6522..3636c5d 100644 --- a/src/components/ui/dashboard-header/index.tsx +++ b/src/components/ui/dashboard-header/index.tsx @@ -15,8 +15,8 @@ const buttonClass = export default function DashboardHeader(): ReactNode { const [isModalOpen, setIsModalOpen] = useState(false); - const router = useRouter().query; - const dashboardId = getStringFromQuery(router, 'dashboardId'); + const router = useRouter(); + const dashboardId = getStringFromQuery(router.query, 'dashboardId'); const { data: myInfo } = useFetch({ asyncFunction: () => getMyInfo(), @@ -46,10 +46,12 @@ export default function DashboardHeader(): ReactNode { handleCloseModal(); }; + const title = pathnameToTitle(router.pathname); + return (
-

내 대시보드

+

{title ?? dashboardData?.title}

{isMyDashboard && ( ); } + +const pathnameToTitle = (pathname: string) => { + switch (pathname) { + case '/mydashboard': { + return '나의 대시보드'; + } + case '/mypage': { + return '계정관리'; + } + default: { + return null; + break; + } + } +}; diff --git a/src/components/ui/side-menu/dashboard-list.tsx b/src/components/ui/side-menu/dashboard-list.tsx index 710b7d2..14358f7 100644 --- a/src/components/ui/side-menu/dashboard-list.tsx +++ b/src/components/ui/side-menu/dashboard-list.tsx @@ -1,16 +1,22 @@ import Image from 'next/image'; +import { useRouter } from 'next/router'; import type { ReactNode } from 'react'; import Dot from '@/components/ui/side-menu/dot'; import type { DashboardType } from '@/lib/dashboards/type'; import { cn } from '@/utils/cn'; import type { dashboardColorType } from '@/utils/dashboard-color'; +import { getStringFromQuery } from '@/utils/getContextQuery'; export default function DashboardList({ dashboards, }: { dashboards: DashboardType[]; }): ReactNode { - const selectedId = 0; + const router = useRouter(); + const dashboardId = getStringFromQuery(router.query, 'dashboardId'); + const handleClickItem = (id: number) => { + router.push(`/dashboard/${String(id)}`); + }; return (
    @@ -19,11 +25,13 @@ export default function DashboardList({
diff --git a/src/components/ui/side-menu/index.tsx b/src/components/ui/side-menu/index.tsx index ad5c546..bba294b 100644 --- a/src/components/ui/side-menu/index.tsx +++ b/src/components/ui/side-menu/index.tsx @@ -1,17 +1,17 @@ import Image from 'next/image'; import Link from 'next/link'; import { type ReactNode, useState } from 'react'; +import CreateNewboardModal from '@/components/mydashboard/create-newboard-modal'; import ButtonPagination from '@/components/ui/button/button-pagination'; +import ModalPortal from '@/components/ui/modal/modal-portal'; import DashboardList from '@/components/ui/side-menu/dashboard-list'; import { useFetch } from '@/hooks/useAsync'; import { getDashBoardList } from '@/lib/dashboards/api'; -import { getMyInfo } from '@/lib/users/api'; export default function SideMenu(): ReactNode { + const [isModalOpen, setIsModalOpen] = useState(false); const [page, setPage] = useState(1); - const { data: myInfo } = useFetch({ - asyncFunction: () => getMyInfo(), - }); + const pageSize = 12; const { data: dashboardListData, loading, @@ -22,21 +22,37 @@ export default function SideMenu(): ReactNode { navigationMethod: 'pagination', cursorId: 0, page, - size: 10, + size: pageSize, }); }, deps: [page], }); + + if (!dashboardListData || error) { + return null; + } + const pageCount = Math.ceil(dashboardListData.totalCount / pageSize); + const isPrevButtonDisabled = page <= 1; + const isNextButtonDisabled = pageCount === page; const handleClickPrev = () => { - console.log('click'); + if (isPrevButtonDisabled) { + return; + } + setPage((prev) => prev - 1); }; const handleClickNext = () => { - console.log('click'); + if (isNextButtonDisabled) { + return; + } + setPage((prev) => prev + 1); + }; + const handleOpenModal = () => { + setIsModalOpen(true); }; - if (!dashboardListData || error) { - return null; - } + const handleCloseModal = () => { + setIsModalOpen(false); + }; return (
@@ -66,7 +82,7 @@ export default function SideMenu(): ReactNode { DashBoards -
+ + +
); } diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index 3c15be4..81c4e6a 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -7,6 +7,7 @@ export default function Document(): ReactNode {
+ - + + + ); } From 9930c57f6545fc007e4776c386252914ec9e6f04 Mon Sep 17 00:00:00 2001 From: geha Date: Sun, 7 Sep 2025 23:35:27 +0900 Subject: [PATCH 7/7] =?UTF-8?q?fix:=20=EC=B4=88=EB=8C=80=ED=95=98=EA=B8=B0?= =?UTF-8?q?=20=EB=AA=A8=EB=8B=AC=20z-index=20=EC=98=A4=EB=A5=98=20portal?= =?UTF-8?q?=EB=A1=9C=20fix=20+=20header=20dropdown=20=EB=AA=A8=EB=B0=94?= =?UTF-8?q?=EC=9D=BC=ED=99=94=EB=A9=B4=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/dashboard-header/header-profile-dropdown.tsx | 2 +- src/components/ui/dashboard-header/index.tsx | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/components/ui/dashboard-header/header-profile-dropdown.tsx b/src/components/ui/dashboard-header/header-profile-dropdown.tsx index a36cad7..eaaed4b 100644 --- a/src/components/ui/dashboard-header/header-profile-dropdown.tsx +++ b/src/components/ui/dashboard-header/header-profile-dropdown.tsx @@ -45,7 +45,7 @@ export default function HeaderProfileDropdwon({ } - + + +
);