diff --git a/src/app/dashboard/[id]/edit/page.tsx b/src/app/dashboard/[id]/edit/page.tsx index 8d1cd97..89d0b6f 100644 --- a/src/app/dashboard/[id]/edit/page.tsx +++ b/src/app/dashboard/[id]/edit/page.tsx @@ -14,9 +14,10 @@ export default function DashBoardEditPage() { const router = useRouter() return ( -
+
+ {/* 돌아가기 버튼 */} - {/* 컨텐츠 박스: 기본 너비 500px, 화면 작으면 100% 최대 500px */} -
+ {/* 콘텐츠 영역 */} +
- {/* 삭제 버튼 영역: 기본 너비 292px, 화면 작으면 100% 최대 292px, 좌측 margin 제거 */} -
+ {/* 삭제 버튼 영역 */} +
diff --git a/src/app/dashboard/[id]/layout.tsx b/src/app/dashboard/[id]/layout.tsx index e1ec752..bd8ae61 100644 --- a/src/app/dashboard/[id]/layout.tsx +++ b/src/app/dashboard/[id]/layout.tsx @@ -10,7 +10,7 @@ export default function AboutLayout({ return ( <> -
+
{children}
diff --git a/src/app/features/dashboard/components/edit/DeleteDashboardButton.tsx b/src/app/features/dashboard/components/edit/DeleteDashboardButton.tsx index 29d4460..63ea45b 100644 --- a/src/app/features/dashboard/components/edit/DeleteDashboardButton.tsx +++ b/src/app/features/dashboard/components/edit/DeleteDashboardButton.tsx @@ -3,6 +3,7 @@ import api from '@lib/axios' import { showError, showSuccess } from '@lib/toast' import { useMutation } from '@tanstack/react-query' +import { useQueryClient } from '@tanstack/react-query' import axios from 'axios' import { useRouter } from 'next/navigation' import React from 'react' @@ -16,6 +17,7 @@ export default function DeleteDashboardButton({ dashboardId, }: DeleteDashboardButtonProps) { const router = useRouter() + const queryClient = useQueryClient() const mutation = useMutation({ mutationFn: async () => { @@ -27,7 +29,10 @@ export default function DeleteDashboardButton({ ) }, onSuccess: () => { - router.push('/dashboard') + // 대시보드 삭제 후 사이드 바 대시보드 목록 쿼리 무효화 + queryClient.invalidateQueries({ queryKey: ['dashboards'] }) + + router.push('/mydashboard') showSuccess('대시보드가 삭제되었습니다') }, onError: (error) => { diff --git a/src/app/features/dashboard/components/edit/EditInvitation.tsx b/src/app/features/dashboard/components/edit/EditInvitation.tsx index a7fa396..58ea12a 100644 --- a/src/app/features/dashboard/components/edit/EditInvitation.tsx +++ b/src/app/features/dashboard/components/edit/EditInvitation.tsx @@ -32,7 +32,6 @@ export default function EditInvitation() { const dashboardId = params.id as string const queryClient = useQueryClient() - const [currentPage, setCurrentPage] = useState(1) const { @@ -53,7 +52,6 @@ export default function EditInvitation() { retry: false, }) - // length가 0인 경우에도 최소 페이지 1로 보장 const totalPages = Math.max( 1, Math.ceil(invitations.length / INVITATION_SIZE), @@ -86,7 +84,6 @@ export default function EditInvitation() { const handlePrev = () => setCurrentPage((p) => Math.max(p - 1, 1)) const handleNext = () => setCurrentPage((p) => Math.min(p + 1, totalPages)) - // 에러 메시지 정리 const errorMessage = isError && axios.isAxiosError(error) ? error.response?.status === 403 @@ -98,37 +95,55 @@ export default function EditInvitation() { return (
- + {/* Header + 초대 버튼 영역 (데스크탑용) */} +
+ + + {/* 데스크탑에서만 보이는 초대 버튼 */} - +
+ {/* 이메일 입력 및 모바일 전용 버튼 */}
- +
+ + + {/* 모바일/태블릿에서만 보이는 초대 버튼 */} + +
+
{isLoading && (

로딩 중...

)} - {errorMessage && (

{errorMessage}

)} - {!isLoading && !errorMessage && currentItems.map((member, index) => { @@ -150,7 +165,6 @@ export default function EditInvitation() {
- - {children &&
{children}
}
) diff --git a/src/app/shared/components/common/Dropdown/Dropdown.tsx b/src/app/shared/components/common/Dropdown/Dropdown.tsx index eb0f953..7a14623 100644 --- a/src/app/shared/components/common/Dropdown/Dropdown.tsx +++ b/src/app/shared/components/common/Dropdown/Dropdown.tsx @@ -1,84 +1,78 @@ -import { useEffect, useRef, useState } from 'react' +import { useEffect, useLayoutEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' -// 드롭다운 컴포넌트 타입 정의 type DropdownProps = { - trigger: React.ReactNode // 드롭다운을 열기 위한 트리거 요소 (버튼, 아이콘 등) - children: React.ReactNode // 드롭다운 내부 콘텐츠 (메뉴 아이템 등) - width?: string // Tailwind 클래스 기반의 너비 설정 (예: 'w-5', 'w-6') - align?: 'left' | 'center' | 'right' // 드롭다운 정렬 방향 - className?: string // 사용자 정의 클래스 + trigger: React.ReactNode + children: React.ReactNode + width?: string + align?: 'left' | 'center' | 'right' + className?: string } -// 드롭다운 컴포넌트 정의 export default function Dropdown({ trigger, children, - width = 'w-6', // 기본 너비 설정 - align = 'left', // 기본 정렬 방향 설정 + width = 'w-6', + align = 'left', + className = '', }: DropdownProps) { - const [open, setOpen] = useState(false) // 드롭다운 열림 여부 상태 - const triggerRef = useRef(null) // 반응형에 따른 위치 조정을 위한 ref 객체 - const menuRef = useRef(null) // 외부 클릭 감지를 위한 드롭다운 메뉴 ref 객체 + const [open, setOpen] = useState(false) + const triggerRef = useRef(null) + const menuRef = useRef(null) const [coords, setCoords] = useState<{ top: number; left: number }>({ top: 0, left: 0, - }) // 드롭다운 메뉴 위치 좌표 상태 + }) - // 드롭다운 열기/닫기 토글 - function toggleOpen() { - setOpen((prev) => !prev) - } - - // Tailwind width 클래스 값을 실제 CSS 너비 값으로 변환 + // Tailwind 너비를 px 값으로 변환 function getWidthValue(width: string): string | undefined { switch (width) { - case 'w-5': // 할 일 카드 모달에서 사용 + case 'w-5': return '5rem' case 'w-6': return '6rem' default: - return undefined // 정의되지 않은 값은 기본 width 적용 + return undefined } } - // 드롭다운이 열릴 때 위치 계산 및 윈도우 이벤트 바인딩 - useEffect(() => { - function updateCoords() { - if (open && triggerRef.current) { - const rect = triggerRef.current.getBoundingClientRect() // 트리거 위치 측정 + // 위치 업데이트 함수 (useLayoutEffect로 변경) + useLayoutEffect(() => { + if (!open) return + function updateCoords() { + if (triggerRef.current) { + const rect = triggerRef.current.getBoundingClientRect() let left = rect.left if (align === 'center') left = rect.left + rect.width / 2 else if (align === 'right') left = rect.right setCoords({ - top: rect.bottom + window.scrollY, // 화면 스크롤 반영 + top: rect.bottom, left, }) } } - if (open) { - updateCoords() - window.addEventListener('resize', updateCoords) - window.addEventListener('scroll', updateCoords) - } + updateCoords() + + window.addEventListener('scroll', updateCoords, { passive: true }) + window.addEventListener('resize', updateCoords) return () => { - window.removeEventListener('resize', updateCoords) window.removeEventListener('scroll', updateCoords) + window.removeEventListener('resize', updateCoords) } }, [open, align]) - // 드롭다운 외부 클릭 시 닫기 + // 외부 클릭 감지해서 닫기 useEffect(() => { - function handleClickOutside(event: MouseEvent) { + function handleClickOutside(e: MouseEvent) { if ( menuRef.current && - !menuRef.current.contains(event.target as Node) && + !menuRef.current.contains(e.target as Node) && triggerRef.current && - !triggerRef.current.contains(event.target as Node) + !triggerRef.current.contains(e.target as Node) ) { setOpen(false) } @@ -86,22 +80,18 @@ export default function Dropdown({ if (open) { document.addEventListener('mousedown', handleClickOutside) - } else { - document.removeEventListener('mousedown', handleClickOutside) } - return () => { document.removeEventListener('mousedown', handleClickOutside) } }, [open]) - // 드롭다운 메뉴 렌더링 (포탈을 이용하여 body로 위치) const menu = open ? createPortal(
{children}
, - document.body, // body에 포탈로 삽입 + document.body, ) : null - // 트리거 요소 + 드롭다운 메뉴 렌더링 return ( <>
setOpen((prev) => !prev)} className="inline-block cursor-pointer" > {trigger} diff --git a/src/app/shared/components/common/UserInfo.tsx b/src/app/shared/components/common/UserInfo.tsx index d195133..30e8418 100644 --- a/src/app/shared/components/common/UserInfo.tsx +++ b/src/app/shared/components/common/UserInfo.tsx @@ -22,9 +22,7 @@ export function UserInfo({ nickname, imageUrl, size = 36 }: UserInfoProps) {
{/* Avatar에 nickname, profileImageUrl 모두 넘겨줌 */} - - {displayNickname} - + {displayNickname}
) } diff --git a/src/app/shared/components/common/header/Collaborator/CollaboratorList.tsx b/src/app/shared/components/common/header/Collaborator/CollaboratorList.tsx index 2a66b73..da1b040 100644 --- a/src/app/shared/components/common/header/Collaborator/CollaboratorList.tsx +++ b/src/app/shared/components/common/header/Collaborator/CollaboratorList.tsx @@ -12,16 +12,16 @@ const MAX_COLLABS = 4 export default function CollaboratorList() { const { id: dashboardId } = useParams() - const dashboardIdStr = String(dashboardId) + const dashboardIdNum = Number(dashboardId) const { data: members = [], isLoading, isError, } = useQuery({ - queryKey: ['members', dashboardIdStr], - queryFn: () => fetchMembers(dashboardIdStr), - enabled: !!dashboardIdStr, + queryKey: ['members', dashboardIdNum], + queryFn: () => fetchMembers(dashboardIdNum), + enabled: !!dashboardIdNum, }) if (isLoading && isError) return null diff --git a/src/app/shared/components/common/header/LeftHeaderContent.tsx b/src/app/shared/components/common/header/LeftHeaderContent.tsx index 4ce27c4..7775adb 100644 --- a/src/app/shared/components/common/header/LeftHeaderContent.tsx +++ b/src/app/shared/components/common/header/LeftHeaderContent.tsx @@ -8,17 +8,22 @@ export default function LeftHeaderContent() { const { selectedDashboard } = useSelectedDashboardStore() const pathname = usePathname() - if (!selectedDashboard) return null + const isMypage = pathname === '/mypage' + const isMyDashboard = pathname === '/mydashboard' + const showCrown = + selectedDashboard?.createdByMe && !isMypage && !isMyDashboard + + const title = isMypage + ? '계정관리' + : isMyDashboard + ? '내 대시보드' + : selectedDashboard?.title || '내 대시보드' return (
-
- {pathname === '/mypage' - ? '계정관리' - : selectedDashboard.title || '내 대시보드'} -
+
{title}
- {selectedDashboard.createdByMe && pathname !== '/mypage' && ( + {showCrown && (
- - openModal('invite')} - iconSrc="/images/invitation.png" - label="초대하기" - /> + {!isMyDashboardPage && ( + <> + + openModal('invite')} + iconSrc="/images/invitation.png" + label="초대하기" + /> + + )} ) } diff --git a/src/app/shared/hooks/useMembers.ts b/src/app/shared/hooks/useMembers.ts index 1fd6bf4..65ad403 100644 --- a/src/app/shared/hooks/useMembers.ts +++ b/src/app/shared/hooks/useMembers.ts @@ -14,7 +14,7 @@ export type Member = { const teamId = getTeamId() -export async function fetchMembers(dashboardId: string): Promise { +export async function fetchMembers(dashboardId: number): Promise { const { data } = await authHttpClient.get(`/${teamId}/members`, { params: { page: 1, diff --git a/tailwind.config.ts b/tailwind.config.ts index c896f70..1af6392 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -19,6 +19,7 @@ const config: Config = { screens: { mobile: { max: '375px' }, // 0 ~ 375px tablet: { raw: '(min-width: 376px) and (max-width: 744px)' }, // 376 ~ 1919px + 'mobile-sm': { max: '500px' }, // 0 ~ 500px 'mobile-wide': { raw: '(min-width: 0px) and (max-width: 683px)' }, // 0 ~ 683px 'tablet-wide': { raw: '(min-width: 684px) and (max-width: 1439px)' }, // 684 ~ 1439px },