diff --git a/src/app/(root)/[teamid]/_domain/components/Kanban/KanbanItem.module.css b/src/app/(root)/[teamid]/_domain/components/Kanban/KanbanItem.module.css index b13f7cb..62b370d 100644 --- a/src/app/(root)/[teamid]/_domain/components/Kanban/KanbanItem.module.css +++ b/src/app/(root)/[teamid]/_domain/components/Kanban/KanbanItem.module.css @@ -8,6 +8,13 @@ .cardWrapper { position: relative; + transition: opacity 0.15s; +} + +/* 서버 요청 진행 중: 카드를 흐리게 표시하고 추가 인터랙션 차단 */ +.cardPending { + opacity: 0.5; + pointer-events: none; } /* 드래그 중인 아이템: TodoCard와 동일한 border-radius(12px)로 테두리 플레이스홀더 표시 */ diff --git a/src/app/(root)/[teamid]/_domain/components/Kanban/KanbanItem.tsx b/src/app/(root)/[teamid]/_domain/components/Kanban/KanbanItem.tsx index 01a9809..c320184 100644 --- a/src/app/(root)/[teamid]/_domain/components/Kanban/KanbanItem.tsx +++ b/src/app/(root)/[teamid]/_domain/components/Kanban/KanbanItem.tsx @@ -105,7 +105,7 @@ function KanbanItem({ >
{isEditing ? (
diff --git a/src/app/(root)/[teamid]/_domain/components/Team/SidebarWrapper.tsx b/src/app/(root)/[teamid]/_domain/components/Team/SidebarWrapper.tsx deleted file mode 100644 index 0156e21..0000000 --- a/src/app/(root)/[teamid]/_domain/components/Team/SidebarWrapper.tsx +++ /dev/null @@ -1,25 +0,0 @@ -'use client'; - -import { useRouter } from 'next/navigation'; -import { Sidebar } from '@/components/sidebar'; -import ProfileImage from '@/components/profile-img/ProfileImage'; -import { useCurrentUserQuery } from '@/shared/queries/user/useCurrentUserQuery'; -import TeamSidebarDropdown from './TeamSidebarDropdown'; - -export default function SidebarWrapper() { - const { data: currentUser } = useCurrentUserQuery(); - const router = useRouter(); - - return ( - } - isLoggedIn={!!currentUser} - profileImage={ - - } - profileName={currentUser?.nickname} - profileTeam={currentUser?.email} - onProfileClick={() => router.push('/mypage')} - /> - ); -} diff --git a/src/app/(root)/[teamid]/_domain/components/Team/TeamNavClient.tsx b/src/app/(root)/[teamid]/_domain/components/Team/TeamNavClient.tsx deleted file mode 100644 index 96cf5eb..0000000 --- a/src/app/(root)/[teamid]/_domain/components/Team/TeamNavClient.tsx +++ /dev/null @@ -1,47 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { useRouter } from 'next/navigation'; - -import { MobileHeader, MobileDrawer } from '@/components/sidebar'; -import ProfileImage from '@/components/profile-img/ProfileImage'; -import { useCurrentUserQuery } from '@/shared/queries/user/useCurrentUserQuery'; -import TeamTabletHeader from './TeamTabletHeader'; -import TeamSidebarDropdown from './TeamSidebarDropdown'; -import styles from './TeamNavClient.module.css'; - -export default function TeamNavClient() { - const [isDrawerOpen, setIsDrawerOpen] = useState(false); - const { data: currentUser } = useCurrentUserQuery(); - const router = useRouter(); - - const openDrawer = () => setIsDrawerOpen(true); - const closeDrawer = () => setIsDrawerOpen(false); - const handleProfileClick = () => router.push('/mypage'); - - return ( - <> - {/* 태블릿 헤더 */} -
- -
- - {/* 모바일 헤더 */} -
- - } - onMenuClick={openDrawer} - onProfileClick={handleProfileClick} - /> -
- - {/* 태블릿/모바일 공통 사이드바 드로어 */} - - - - - ); -} diff --git a/src/app/(root)/[teamid]/_domain/hooks/useKanbanTasks.ts b/src/app/(root)/[teamid]/_domain/hooks/useKanbanTasks.ts index 6ec06b6..227e88c 100644 --- a/src/app/(root)/[teamid]/_domain/hooks/useKanbanTasks.ts +++ b/src/app/(root)/[teamid]/_domain/hooks/useKanbanTasks.ts @@ -131,9 +131,18 @@ export function useKanbanTasks( }), ); + // 2초 이상 응답이 없을 때만 pending 표시 (빠른 응답 시 깜빡임 방지) + const pendingTimer = window.setTimeout(() => { + setTasks((prev) => + prev.map((task) => (task.id === taskId ? { ...task, pending: true } : task)), + ); + }, 2000); + try { await updateTask(groupId, taskListId, Number(itemId), { done: checked }); } finally { + // 타이머가 남아있으면 취소 (pending 노출 전에 완료된 경우) + clearTimeout(pendingTimer); // 성공/실패 관계없이 서버 상태와 동기화 await queryClient.invalidateQueries({ queryKey }); } diff --git a/src/app/(root)/[teamid]/_domain/interfaces/team.ts b/src/app/(root)/[teamid]/_domain/interfaces/team.ts index 8e04562..a9c847d 100644 --- a/src/app/(root)/[teamid]/_domain/interfaces/team.ts +++ b/src/app/(root)/[teamid]/_domain/interfaces/team.ts @@ -18,6 +18,8 @@ export interface KanbanTask { title: string; items: TaskItem[]; status: KanbanStatus; + /** 서버 요청 진행 중 여부 (Soft Optimistic UI용) */ + pending?: boolean; } export interface MockTeam { diff --git a/src/app/(root)/[teamid]/page.module.css b/src/app/(root)/[teamid]/page.module.css index 6ea7c63..6cb330a 100644 --- a/src/app/(root)/[teamid]/page.module.css +++ b/src/app/(root)/[teamid]/page.module.css @@ -13,7 +13,6 @@ min-width: 0; width: 100%; /* Explicitly set width to 100% */ box-sizing: border-box; /* Ensure padding is included in the width */ - margin-left: 24px; } /* Apply max-width to direct children of mainContents to prevent overflow */ @@ -26,8 +25,9 @@ .desktopSidebar { display: block; /* Show desktop sidebar */ /* fixed 포지션 사이드바가 flex 흐름에서 공간을 차지하지 않으므로, spacer 역할을 위해 너비 명시 */ - width: 270px; - min-width: 270px; + /* 루트 레이아웃 main의 margin-left: 72px를 이미 반영하므로 270 - 72 = 198px */ + width: 198px; + min-width: 198px; flex-shrink: 0; } /* Tablet styles */ diff --git a/src/app/(root)/addteam/_domain/components/AddTeamSidebarWrapper.tsx b/src/app/(root)/addteam/_domain/components/AddTeamSidebarWrapper.tsx index 35227aa..5ec4639 100644 --- a/src/app/(root)/addteam/_domain/components/AddTeamSidebarWrapper.tsx +++ b/src/app/(root)/addteam/_domain/components/AddTeamSidebarWrapper.tsx @@ -9,6 +9,11 @@ export default function AddTeamSidebarWrapper() { const { data: currentUser } = useCurrentUserQuery(); const router = useRouter(); + const handleLogout = async () => { + await fetch('/api/auth/logout', { method: 'POST' }); + router.push('/login'); + }; + return ( router.push('/mypage')} + onLogout={handleLogout} /> ); }