diff --git a/public/images/next-disabled.png b/public/images/next-disabled.png new file mode 100644 index 0000000..8c6c5c0 Binary files /dev/null and b/public/images/next-disabled.png differ diff --git a/public/images/next.png b/public/images/next.png index 8c6c5c0..0037479 100644 Binary files a/public/images/next.png and b/public/images/next.png differ diff --git a/public/images/prev-disabled.png b/public/images/prev-disabled.png new file mode 100644 index 0000000..b13ea1a Binary files /dev/null and b/public/images/prev-disabled.png differ diff --git a/public/images/prev.png b/public/images/prev.png index b13ea1a..293d912 100644 Binary files a/public/images/prev.png and b/public/images/prev.png differ diff --git a/src/app/dashboard/[id]/edit/components/EditInfo.tsx b/src/app/dashboard/[id]/edit/components/EditInfo.tsx deleted file mode 100644 index a4f5eb1..0000000 --- a/src/app/dashboard/[id]/edit/components/EditInfo.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function EditInfo() { - return

대시보드 수정 페이지

-} diff --git a/src/app/dashboard/[id]/edit/components/EditInvitation.tsx b/src/app/dashboard/[id]/edit/components/EditInvitation.tsx deleted file mode 100644 index ffa2750..0000000 --- a/src/app/dashboard/[id]/edit/components/EditInvitation.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function EditInvitation() { - return

대시보드 초대 수정 페이지

-} diff --git a/src/app/dashboard/[id]/edit/components/EditMember.tsx b/src/app/dashboard/[id]/edit/components/EditMember.tsx deleted file mode 100644 index 3cf5295..0000000 --- a/src/app/dashboard/[id]/edit/components/EditMember.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function EditMember() { - return

대시보드 멤버 수정 페이지

-} diff --git a/src/app/dashboard/[id]/edit/layout.tsx b/src/app/dashboard/[id]/edit/layout.tsx index e1ec752..7d27881 100644 --- a/src/app/dashboard/[id]/edit/layout.tsx +++ b/src/app/dashboard/[id]/edit/layout.tsx @@ -1,19 +1,11 @@ -import Header from '@components/common/header/Header' - -import Sidebar from '@/app/shared/components/common/sidebar/Sidebar' - export default function AboutLayout({ children, }: { children: React.ReactNode }) { return ( - <> - -
-
-
{children}
-
- +
+
{children}
+
) } diff --git a/src/app/dashboard/[id]/edit/page.tsx b/src/app/dashboard/[id]/edit/page.tsx index 63530c2..43864a9 100644 --- a/src/app/dashboard/[id]/edit/page.tsx +++ b/src/app/dashboard/[id]/edit/page.tsx @@ -5,8 +5,15 @@ import EditInvitation from '@dashboard/components/edit/EditInvitation' import EditMember from '@dashboard/components/edit/EditMember' import { showError, showSuccess } from '@lib/toast' import Image from 'next/image' +import { useParams } from 'next/navigation' +import { useRouter } from 'next/navigation' + +import DeleteDashboardButton from '@/app/features/dashboard/components/edit/DeleteDashboardButton' export default function DashBoardEditPage() { + const { id } = useParams() + const router = useRouter() + const handleSuccess = () => { showSuccess('대시보드가 성공적으로 수정되었습니다.') } @@ -16,22 +23,25 @@ export default function DashBoardEditPage() { } return ( -
-
+
+
- + + {/* 삭제 버튼 영역 */} +
+ +
) } diff --git a/src/app/dashboard/[id]/layout.tsx b/src/app/dashboard/[id]/layout.tsx index f7f1488..e1ec752 100644 --- a/src/app/dashboard/[id]/layout.tsx +++ b/src/app/dashboard/[id]/layout.tsx @@ -8,10 +8,12 @@ export default function AboutLayout({ children: React.ReactNode }) { return ( -
- {/* */} -
-
{children}
{/* 여기에 page.tsx 내용이 들어옴 */} -
+ <> + +
+
+
{children}
+
+ ) } diff --git a/src/app/features/auth/store/useAuthStore.ts b/src/app/features/auth/store/useAuthStore.ts index 29b4470..b6bd5c6 100644 --- a/src/app/features/auth/store/useAuthStore.ts +++ b/src/app/features/auth/store/useAuthStore.ts @@ -1,5 +1,6 @@ import { create } from 'zustand' import { persist } from 'zustand/middleware' + import { AuthState } from '../types/auth.type' export const useAuthStore = create()( diff --git a/src/app/features/dashboard/api/invitation.ts b/src/app/features/dashboard/api/invitation.ts new file mode 100644 index 0000000..c0d9db2 --- /dev/null +++ b/src/app/features/dashboard/api/invitation.ts @@ -0,0 +1,17 @@ +// src/app/features/dashboard/api/invitation.ts +import api from '@lib/axios' + +type InvitationRequest = { + email: string + dashboardId: number | string +} + +export const inviteUser = async ({ email, dashboardId }: InvitationRequest) => { + const response = await api.post( + `/${process.env.NEXT_PUBLIC_TEAM_ID}/dashboards/${dashboardId}/invitations`, + { + email, + }, + ) + return response.data +} diff --git a/src/app/features/dashboard/components/edit/DeleteDashboardButton.tsx b/src/app/features/dashboard/components/edit/DeleteDashboardButton.tsx new file mode 100644 index 0000000..c190631 --- /dev/null +++ b/src/app/features/dashboard/components/edit/DeleteDashboardButton.tsx @@ -0,0 +1,67 @@ +'use client' + +import api from '@lib/axios' +import axios from 'axios' +import { useRouter } from 'next/navigation' +import React, { useState } from 'react' + +type DeleteDashboardButtonProps = { + dashboardId: string +} + +export default function DeleteDashboardButton({ + dashboardId, +}: DeleteDashboardButtonProps) { + const router = useRouter() + const [isDeleting, setIsDeleting] = useState(false) + + console.log('DeleteDashboardButton 렌더됨:', dashboardId) + + const handleDelete = async () => { + const confirmed = confirm( + '정말로 이 대시보드를 삭제하시겠습니까? 삭제 후 되돌릴 수 없습니다.', + ) + + if (!confirmed) return + + try { + setIsDeleting(true) + + if (!process.env.NEXT_PUBLIC_TEAM_ID) { + throw new Error('NEXT_PUBLIC_TEAM_ID 환경변수가 설정되지 않았습니다.') + } + + await api.delete( + `/${process.env.NEXT_PUBLIC_TEAM_ID}/dashboards/${dashboardId}`, + ) + + // 삭제 후 대시보드 목록 페이지로 이동 + router.push('/dashboard') + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + const message = + error.response?.data?.message || + '대시보드 삭제 중 오류가 발생했습니다.' + console.error('대시보드 삭제 오류:', message) + alert(message) // 또는 showError(message) 등으로 사용자에게 표시 + } else { + console.error('대시보드 삭제 오류:', error) + alert('알 수 없는 오류가 발생했습니다.') + } + } finally { + setIsDeleting(false) + } + } + + return ( + + ) +} diff --git a/src/app/features/dashboard/components/edit/EditInfo.tsx b/src/app/features/dashboard/components/edit/EditInfo.tsx index 1053973..4b6b5c3 100644 --- a/src/app/features/dashboard/components/edit/EditInfo.tsx +++ b/src/app/features/dashboard/components/edit/EditInfo.tsx @@ -1,21 +1,39 @@ +'use client' + import api from '@lib/axios' +import { useQueryClient } from '@tanstack/react-query' +import axios from 'axios' import Image from 'next/image' import { useRouter } from 'next/navigation' import React, { useEffect, useState } from 'react' import { DASHBOARD_COLORS } from '@/app/shared/constants/colors' +import { useSelectedDashboardStore } from '@/app/shared/store/useSelectedDashboardStore' import { CreateDashboardRequest } from '@/app/shared/types/dashboard' export default function EditInfo() { const router = useRouter() + const { selectedDashboard, setSelectedDashboard } = + useSelectedDashboardStore() + const queryClient = useQueryClient() + const [formData, setFormData] = useState({ title: '', color: DASHBOARD_COLORS[0], }) - const [isSubmitting, setIsSubmitting] = useState(false) - /// 입력값 변경 처리 + // selectedDashboard가 있을 때 formData 초기화 + useEffect(() => { + if (selectedDashboard) { + setFormData({ + title: selectedDashboard.title, + color: selectedDashboard.color, + }) + } + }, [selectedDashboard]) + + // 입력값 변경 핸들러 const handleChange = (e: React.ChangeEvent) => { const { name, value } = e.target setFormData((prev) => ({ @@ -23,34 +41,51 @@ export default function EditInfo() { [name]: value, })) } - // 색상 선택 처리 + + // 색상 선택 핸들러 const handleColorSelect = (color: string) => { setFormData((prev) => ({ ...prev, color })) } + + // 제출 핸들러 const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() - if (!formData.title || !formData.color) { - return - } + if (!formData.title || !formData.color) return + try { setIsSubmitting(true) - if (!process.env.NEXT_PUBLIC_TEAM_ID) { - throw new Error('NEXT_PUBLIC_TEAM_ID 환경변수가 설정되지 않았습니다.') + if (!process.env.NEXT_PUBLIC_TEAM_ID || !selectedDashboard?.id) { + throw new Error('필수 정보가 누락되었습니다.') } - const response = await api.post( - `/${process.env.NEXT_PUBLIC_TEAM_ID}/dashboards`, + const response = await api.put( + `/${process.env.NEXT_PUBLIC_TEAM_ID}/dashboards/${selectedDashboard.id}`, formData, ) const data = response.data - // 성공 시 대시보드 상세 페이지로 이동 - router.push(`/dashboard/${data.id}`) - } catch (error) { - console.error('대시보드 생성 오류:', error) + // 1. 상태 업데이트 (헤더, 수정정보 실시간 반영) + setSelectedDashboard(data) + + // 2. react-query 캐시 무효화 → Sidebar 목록 재요청 유도 + await queryClient.invalidateQueries({ queryKey: ['dashboards'] }) + + // 성공 시 상세 페이지 이동 + router.push(`/dashboard/${data.id}/edit`) + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + const message = + error.response?.data?.message || + '대시보드 수정 중 오류가 발생했습니다.' + console.error('대시보드 수정 오류:', message) + alert(message) + } else { + console.error('대시보드 수정 오류:', error) + alert('알 수 없는 오류가 발생했습니다.') + } } finally { setIsSubmitting(false) } @@ -60,7 +95,9 @@ export default function EditInfo() {
{/* 컨테이너 */}
-

새로운 대시보드

+

+ {selectedDashboard?.title || '대시보드 편집'} +

{/* 제목 입력 */} @@ -92,7 +129,7 @@ export default function EditInfo() { style={{ backgroundColor: color }} aria-label={`색상 ${color}`} > - {/* 선택된 색상 체크 */} + {/* 선택된 색상에 체크 표시 */} {formData.color === color && (
{ + if (currentPage > 1) { + setCurrentPage((prev) => prev - 1) + } + } + + const handleNext = () => { + if (currentPage < totalPages) { + setCurrentPage((prev) => prev + 1) + } + } + return (
{/* 컨테이너 */} @@ -17,11 +41,35 @@ export default function EditInvitation() {

초대 내역

-

1 페이지 중 1

- 이전 - 다음 - + {totalPages} 페이지 중 {currentPage} +

+ + +

초대하기

- +
@@ -44,21 +92,26 @@ export default function EditInvitation() { 이메일
- {mockMembers.map((member, index) => ( -
- { + const isLast = index === paginationMembers.length - 1 + return ( +
- -
- ))} + className={`flex items-center justify-between py-4 ${ + !isLast ? 'Border-bottom' : '' + }`} + > + + +
+ ) + })}
diff --git a/src/app/features/dashboard/components/edit/EditMember.tsx b/src/app/features/dashboard/components/edit/EditMember.tsx index 674cc34..b5ed11d 100644 --- a/src/app/features/dashboard/components/edit/EditMember.tsx +++ b/src/app/features/dashboard/components/edit/EditMember.tsx @@ -1,11 +1,35 @@ import Image from 'next/image' import React from 'react' +import { useState } from 'react' import { UserInfo } from '@/app/shared/components/common/UserInfo' import { mockMembers } from './mockMember' +const PAGE_SIZE = 4 // 페이지당 표시할 구성원 수 + export default function EditMember() { + const [currentPage, setCurrentPage] = useState(1) + const totalPages = Math.ceil(mockMembers.length / PAGE_SIZE) + + const startIndex = (currentPage - 1) * PAGE_SIZE + const paginationMembers = mockMembers.slice( + startIndex, + startIndex + PAGE_SIZE, + ) + + const handlePrev = () => { + if (currentPage > 1) { + setCurrentPage((prev) => prev - 1) + } + } + + const handleNext = () => { + if (currentPage < totalPages) { + setCurrentPage((prev) => prev + 1) + } + } + return (
{/* 컨테이너 */} @@ -14,9 +38,33 @@ export default function EditMember() {

구성원

-

1 페이지 중 1

- 이전 - 다음 +

+ {totalPages} 페이지 중 {currentPage} +

+ +
@@ -24,22 +72,27 @@ export default function EditMember() { -
- {mockMembers.map((member, index) => ( -
- + {paginationMembers.map((member, index) => { + // 해당 페이지 중 마지막 요소인 경우 border-bottom 미적용 + const isLast = index === paginationMembers.length - 1 + return ( +
- -
- ))} + className={`flex items-center justify-between py-12 ${ + !isLast ? 'Border-bottom' : '' + }`} + > + + +
+ ) + })}
diff --git a/src/app/features/dashboard/components/edit/mockMember.js b/src/app/features/dashboard/components/edit/mockMember.js index 473e07d..83c5a2b 100644 --- a/src/app/features/dashboard/components/edit/mockMember.js +++ b/src/app/features/dashboard/components/edit/mockMember.js @@ -21,4 +21,8 @@ export const mockMembers = [ nickname: 'Emily', imageUrl: null, }, + { + nickname: 'Jenny', + imageUrl: null, + }, ] diff --git a/src/app/layout.tsx b/src/app/layout.tsx index aa8246e..6afe015 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,6 +4,7 @@ import type { Metadata } from 'next' import { Toaster } from 'sonner' import { Providers } from './providers' +import GlobalModalRenderer from './shared/components/common/GlobalModalRender' export const metadata: Metadata = { title: 'Coplan', @@ -23,6 +24,7 @@ export default function RootLayout({ {children} +