Skip to content

Commit 4b65c0f

Browse files
committed
Merge branch 'develop' into refactor/chanho
2 parents c45744e + 9169f13 commit 4b65c0f

24 files changed

+762
-588
lines changed

public/images/management.png

-844 Bytes
Binary file not shown.

src/app/dashboard/[id]/edit/page.tsx

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import EditInfo from '@dashboard/components/edit/EditInfo'
44
import EditInvitation from '@dashboard/components/edit/EditInvitation'
55
import EditMember from '@dashboard/components/edit/EditMember'
6-
import { showError, showSuccess } from '@lib/toast'
76
import Image from 'next/image'
87
import { useParams } from 'next/navigation'
98
import { useRouter } from 'next/navigation'
@@ -14,23 +13,15 @@ export default function DashBoardEditPage() {
1413
const { id } = useParams()
1514
const router = useRouter()
1615

17-
const handleSuccess = () => {
18-
showSuccess('대시보드가 성공적으로 수정되었습니다.')
19-
}
20-
21-
const handleError = () => {
22-
showError('수정 중 오류가 발생했습니다.')
23-
}
24-
2516
return (
2617
<div className="BG-gray pb-16">
2718
<button
28-
className="flex cursor-pointer items-center gap-12 p-16"
19+
className="flex cursor-pointer items-center gap-12 px-16 pt-16"
2920
type="button"
3021
onClick={() => router.back()}
3122
>
32-
<Image src="/images/back.png" alt="돌아가기" width={8} height={4} />
33-
<p>돌아가기</p>
23+
<Image src="/images/back.png" alt="돌아가기" width={6} height={4} />
24+
<p className="text-14">돌아가기</p>
3425
</button>
3526
<div className="flex w-500 flex-col gap-16 p-16">
3627
<EditInfo />

src/app/features/dashboard/components/edit/DeleteDashboardButton.tsx

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
'use client'
22

33
import api from '@lib/axios'
4+
import { showError, showSuccess } from '@lib/toast'
5+
import { useMutation } from '@tanstack/react-query'
46
import axios from 'axios'
57
import { useRouter } from 'next/navigation'
6-
import React, { useState } from 'react'
8+
import React from 'react'
9+
import { toast } from 'sonner'
710

811
type DeleteDashboardButtonProps = {
912
dashboardId: string
@@ -13,52 +16,52 @@ export default function DeleteDashboardButton({
1316
dashboardId,
1417
}: DeleteDashboardButtonProps) {
1518
const router = useRouter()
16-
const [isDeleting, setIsDeleting] = useState(false)
17-
18-
console.log('DeleteDashboardButton 렌더됨:', dashboardId)
19-
20-
const handleDelete = async () => {
21-
const confirmed = confirm(
22-
'정말로 이 대시보드를 삭제하시겠습니까? 삭제 후 되돌릴 수 없습니다.',
23-
)
24-
25-
if (!confirmed) return
26-
27-
try {
28-
setIsDeleting(true)
2919

20+
const mutation = useMutation<void, Error, void>({
21+
mutationFn: async () => {
3022
if (!process.env.NEXT_PUBLIC_TEAM_ID) {
3123
throw new Error('NEXT_PUBLIC_TEAM_ID 환경변수가 설정되지 않았습니다.')
3224
}
33-
3425
await api.delete(
3526
`/${process.env.NEXT_PUBLIC_TEAM_ID}/dashboards/${dashboardId}`,
3627
)
37-
38-
// 삭제 후 대시보드 목록 페이지로 이동
28+
},
29+
onSuccess: () => {
3930
router.push('/dashboard')
40-
} catch (error: unknown) {
31+
showSuccess('대시보드가 삭제되었습니다')
32+
},
33+
onError: (error) => {
4134
if (axios.isAxiosError(error)) {
4235
const message =
4336
error.response?.data?.message ||
4437
'대시보드 삭제 중 오류가 발생했습니다.'
45-
console.error('대시보드 삭제 오류:', message)
46-
alert(message) // 또는 showError(message) 등으로 사용자에게 표시
38+
showError(message)
4739
} else {
48-
console.error('대시보드 삭제 오류:', error)
49-
alert('알 수 없는 오류가 발생했습니다.')
40+
showError('알 수 없는 오류가 발생했습니다.')
5041
}
51-
} finally {
52-
setIsDeleting(false)
53-
}
42+
},
43+
})
44+
45+
// sonner로 삭제 확인 토스트 구현
46+
function handleDelete() {
47+
toast('대시보드를 삭제하시겠습니까?', {
48+
description: '삭제 후 되돌릴 수 없습니다.',
49+
action: {
50+
label: '삭제하기',
51+
onClick: () => mutation.mutate(),
52+
},
53+
})
5454
}
5555

5656
return (
5757
<button
5858
onClick={handleDelete}
59-
disabled={isDeleting}
59+
// isLoading -> isPending으로 수정됨
60+
disabled={mutation.isPending}
6061
className={`Text-black my-8 rounded-8 font-semibold transition-opacity ${
61-
isDeleting ? 'cursor-not-allowed opacity-50' : 'hover:opacity-90'
62+
mutation.isPending
63+
? 'cursor-not-allowed opacity-50'
64+
: 'hover:opacity-90'
6265
}`}
6366
>
6467
대시보드 삭제하기

src/app/features/dashboard/components/edit/EditInfo.tsx

Lines changed: 20 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -1,165 +1,35 @@
11
'use client'
22

3-
import api from '@lib/axios'
4-
import { useQueryClient } from '@tanstack/react-query'
5-
import axios from 'axios'
6-
import Image from 'next/image'
7-
import { useRouter } from 'next/navigation'
8-
import React, { useEffect, useState } from 'react'
9-
10-
import { DASHBOARD_COLORS } from '@/app/shared/constants/colors'
11-
import { useSelectedDashboardStore } from '@/app/shared/store/useSelectedDashboardStore'
12-
import { CreateDashboardRequest } from '@/app/shared/types/dashboard'
3+
import DashboardForm from '@components/dashboard/DashboardForm'
4+
import { useDashboardForm } from '@hooks/useDashboardForm'
5+
import React from 'react'
136

147
export default function EditInfo() {
15-
const router = useRouter()
16-
const { selectedDashboard, setSelectedDashboard } =
17-
useSelectedDashboardStore()
18-
const queryClient = useQueryClient()
19-
20-
const [formData, setFormData] = useState<CreateDashboardRequest>({
21-
title: '',
22-
color: DASHBOARD_COLORS[0],
23-
})
24-
const [isSubmitting, setIsSubmitting] = useState(false)
25-
26-
// selectedDashboard가 있을 때 formData 초기화
27-
useEffect(() => {
28-
if (selectedDashboard) {
29-
setFormData({
30-
title: selectedDashboard.title,
31-
color: selectedDashboard.color,
32-
})
33-
}
34-
}, [selectedDashboard])
35-
36-
// 입력값 변경 핸들러
37-
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
38-
const { name, value } = e.target
39-
setFormData((prev) => ({
40-
...prev,
41-
[name]: value,
42-
}))
43-
}
44-
45-
// 색상 선택 핸들러
46-
const handleColorSelect = (color: string) => {
47-
setFormData((prev) => ({ ...prev, color }))
48-
}
49-
50-
// 제출 핸들러
51-
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
52-
e.preventDefault()
53-
54-
if (!formData.title || !formData.color) return
55-
56-
try {
57-
setIsSubmitting(true)
58-
59-
if (!process.env.NEXT_PUBLIC_TEAM_ID || !selectedDashboard?.id) {
60-
throw new Error('필수 정보가 누락되었습니다.')
61-
}
62-
63-
const response = await api.put(
64-
`/${process.env.NEXT_PUBLIC_TEAM_ID}/dashboards/${selectedDashboard.id}`,
65-
formData,
66-
)
67-
68-
const data = response.data
69-
70-
// 1. 상태 업데이트 (헤더, 수정정보 실시간 반영)
71-
setSelectedDashboard(data)
72-
73-
// 2. react-query 캐시 무효화 → Sidebar 목록 재요청 유도
74-
await queryClient.invalidateQueries({ queryKey: ['dashboards'] })
75-
76-
// 성공 시 상세 페이지 이동
77-
router.push(`/dashboard/${data.id}/edit`)
78-
} catch (error: unknown) {
79-
if (axios.isAxiosError(error)) {
80-
const message =
81-
error.response?.data?.message ||
82-
'대시보드 수정 중 오류가 발생했습니다.'
83-
console.error('대시보드 수정 오류:', message)
84-
alert(message)
85-
} else {
86-
console.error('대시보드 수정 오류:', error)
87-
alert('알 수 없는 오류가 발생했습니다.')
88-
}
89-
} finally {
90-
setIsSubmitting(false)
91-
}
92-
}
8+
const {
9+
formData,
10+
isSubmitting,
11+
handleChange,
12+
handleColorSelect,
13+
handleSubmit,
14+
selectedDashboard,
15+
} = useDashboardForm('edit')
9316

9417
return (
9518
<div>
96-
{/* 컨테이너 */}
9719
<div className="BG-white h-300 w-584 rounded-16 px-32 py-24">
9820
<h2 className="Text-black mb-24 text-18 font-bold">
9921
{selectedDashboard?.title || '대시보드 편집'}
10022
</h2>
10123

102-
<form onSubmit={handleSubmit}>
103-
{/* 제목 입력 */}
104-
<div className="mb-16">
105-
<label htmlFor="title" className="Text-black mb-8 block text-16">
106-
대시보드 이름
107-
</label>
108-
<input
109-
type="text"
110-
id="title"
111-
name="title"
112-
value={formData.title}
113-
onChange={handleChange}
114-
placeholder="대시보드 이름을 입력해주세요."
115-
className="Border-section w-512 rounded-8 px-12 py-10 text-16 outline-none"
116-
required
117-
/>
118-
</div>
119-
120-
{/* 색상 선택 */}
121-
<div className="mb-30">
122-
<div className="flex gap-8">
123-
{DASHBOARD_COLORS.map((color) => (
124-
<button
125-
key={color}
126-
type="button"
127-
onClick={() => handleColorSelect(color)}
128-
className="relative flex size-30 items-center justify-center rounded-full"
129-
style={{ backgroundColor: color }}
130-
aria-label={`색상 ${color}`}
131-
>
132-
{/* 선택된 색상에 체크 표시 */}
133-
{formData.color === color && (
134-
<div className="relative size-24 items-center justify-center">
135-
<Image
136-
src="/images/check.svg"
137-
alt="check"
138-
fill
139-
className="object-contain"
140-
/>
141-
</div>
142-
)}
143-
</button>
144-
))}
145-
</div>
146-
</div>
147-
148-
{/* 하단 버튼 */}
149-
<div>
150-
<button
151-
type="submit"
152-
disabled={!formData.title || !formData.color || isSubmitting}
153-
className={`BG-violet h-48 w-512 rounded-8 px-16 py-10 text-16 font-semibold text-white transition-opacity ${
154-
!formData.title || !formData.color || isSubmitting
155-
? 'cursor-not-allowed opacity-50'
156-
: 'hover:opacity-90'
157-
}`}
158-
>
159-
변경
160-
</button>
161-
</div>
162-
</form>
24+
<DashboardForm
25+
formData={formData}
26+
onChange={handleChange}
27+
onColorSelect={handleColorSelect}
28+
onSubmit={handleSubmit}
29+
isSubmitting={isSubmitting}
30+
submitText="변경"
31+
submitButtonWidth="w-516"
32+
/>
16333
</div>
16434
</div>
16535
)

0 commit comments

Comments
 (0)