Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
d1a71ae
🫧modify: 함수명 네이밍 컨벤션 일치
yuj2n Jun 20, 2025
191b9e1
🫧modify: 매직 넘버명 구체화
yuj2n Jun 20, 2025
1dba3aa
🫧modify: 미사용 토스트 구현 삭제
yuj2n Jun 20, 2025
a9bbf42
✨feat: 대시보드 이름 및 색상 변경 폼 공용화
yuj2n Jun 20, 2025
72cad00
🎨style: 동일 이미지 삭제 및 반영
yuj2n Jun 20, 2025
d7573ff
🫧modify: 이전 멤버 컴포넌트 수정
yuj2n Jun 20, 2025
ed4b3a4
🫧modify: pathname 미사용 및 cn 적용
yuj2n Jun 20, 2025
d92ae84
🫧modify: 활성화된 경우 폰트 굵기 적용한 부분 삭제
yuj2n Jun 20, 2025
d62aa07
✨feat: 페이지네이션 커스텀 훅 구현
yuj2n Jun 20, 2025
bc1bc7f
✨feat: 페이지네이션 컴포넌트 작성
yuj2n Jun 20, 2025
cda1787
✨feat: 페이지네이션 훅 및 컴포넌트 적용
yuj2n Jun 20, 2025
8e956d8
🫧modify: 돌아가기 버튼 사이즈 수정
yuj2n Jun 20, 2025
e5fd16a
🫧modify: 조건부 스타일링 삭제에도 cn 사용 이유 주석 추가
yuj2n Jun 20, 2025
97ab479
✨feat: 대시보드 폼 훅으로 핸들러와 상태 분리
yuj2n Jun 20, 2025
9584f87
🫧modify: Input 공통 컴포넌트 적용
yuj2n Jun 20, 2025
972ef58
🐛fix: 사이드바에 대시보드 명 적용안되던 문제 캐시 무효화 및 페이지 강제 갱신으로 해결
yuj2n Jun 20, 2025
458a292
✨feat: useMutation 적용으로 수동 setState 간소화하여 자동 상태 관리
yuj2n Jun 20, 2025
66ff654
🫧modify: 모달타입 수정 적용
yuj2n Jun 21, 2025
b5c3ea2
✨feat: 초대 후 초대 내역 새로고침을 위한 캐시 무효화 구현
yuj2n Jun 21, 2025
d87eb01
✨feat: 초대 내역 불러오기 API 구현 및 툴팁 표시 구현
yuj2n Jun 21, 2025
997294d
✨feat: 대시보드 멤버 목록 가져오는 훅 구현
yuj2n Jun 21, 2025
cf0a06a
🫧modify: 이메일 초대 토스트 적용
yuj2n Jun 21, 2025
71b9013
✨feat: 대시보드 구성원 수정 컴포넌트 구현
yuj2n Jun 21, 2025
93570df
🫧modify: 헤더 버튼 다크모드 반영을 위한 색 추가
yuj2n Jun 21, 2025
05e1f67
✨feat: 로그인된 사용자 및 공동 사용자 정보를 가져오기 위한 데이터 반영
yuj2n Jun 21, 2025
915bd6f
✨feat: 공동 작업자 프로필 api 적용 및 수정
yuj2n Jun 21, 2025
eaeeffc
✨feat: 로그인된 사용자 정보 불러오기 및 로그아웃 시 로그인 화면으로 이동
yuj2n Jun 21, 2025
d06b980
🫧modify: getColor 커스텀 훅 적용
yuj2n Jun 21, 2025
e925703
🫧modify: 안 쓰는 mock 데이터 삭제
yuj2n Jun 21, 2025
5f97a42
🫧modify: 환경변수 설정 여부 체크 및 팀 id 가져오는 함수 재사용성을 위한 분리
yuj2n Jun 21, 2025
3f0097a
✨feat: 대시보드 삭제 시 토스트 띄우기 및 삭제 여부 묻기
yuj2n Jun 21, 2025
a5d5361
🫧modify: 정보 수정 컴포넌트 경로 간소화
yuj2n Jun 21, 2025
280e128
✨feat: 초대 내역 없어도 최소 페이지 1로 보장 및 초대 권한 여부 표시
yuj2n Jun 21, 2025
580eb18
🫧modify: 함수명 네이밍 컨벤션과 글로벌 색상 적용 및 대시보드 생성자 왕관 아이콘 추가
yuj2n Jun 21, 2025
e4d472d
🎨style: 사용자 프로필 이미지와 닉네임 거리 수정
yuj2n Jun 21, 2025
0ad9f9d
🎨style: 불필요한 폰트 색상 제거 및 상위로 필요 색상 이동
yuj2n Jun 21, 2025
1625af9
🎨style: 프로필 이미지 크기 조정 및 헤더 에러 메시지 안좋아보일 것 같아 null로 수정
yuj2n Jun 21, 2025
c324b94
🫧modify: 대시보드 훅 경로 축소
yuj2n Jun 21, 2025
e945eb3
🫧modify: 토스트 및 팀id 함수 적용과 경로 축소
yuj2n Jun 21, 2025
ecebc70
🫧modify: 멤버 훅에 팀 id 함수 적용
yuj2n Jun 21, 2025
496d829
Merge branch 'develop' into feature/dashboard_modify-API
yuj2n Jun 21, 2025
0c78d1d
🫧modify: Avatar 컴포넌트 prop 명 변경에 따른 사용처에 수정사항 적용
yuj2n Jun 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed public/images/management.png
Binary file not shown.
15 changes: 3 additions & 12 deletions src/app/dashboard/[id]/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import EditInfo from '@dashboard/components/edit/EditInfo'
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'
Expand All @@ -14,23 +13,15 @@ export default function DashBoardEditPage() {
const { id } = useParams()
const router = useRouter()

const handleSuccess = () => {
showSuccess('대시보드가 성공적으로 수정되었습니다.')
}

const handleError = () => {
showError('수정 중 오류가 발생했습니다.')
}

return (
<div className="BG-gray pb-16">
<button
className="flex cursor-pointer items-center gap-12 p-16"
className="flex cursor-pointer items-center gap-12 px-16 pt-16"
type="button"
onClick={() => router.back()}
>
<Image src="/images/back.png" alt="돌아가기" width={8} height={4} />
<p>돌아가기</p>
<Image src="/images/back.png" alt="돌아가기" width={6} height={4} />
<p className="text-14">돌아가기</p>
</button>
<div className="flex w-500 flex-col gap-16 p-16">
<EditInfo />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
'use client'

import api from '@lib/axios'
import { showError, showSuccess } from '@lib/toast'
import { useMutation } from '@tanstack/react-query'
import axios from 'axios'
import { useRouter } from 'next/navigation'
import React, { useState } from 'react'
import React from 'react'
import { toast } from 'sonner'

type DeleteDashboardButtonProps = {
dashboardId: string
Expand All @@ -13,52 +16,52 @@ 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)

const mutation = useMutation<void, Error, void>({
mutationFn: async () => {
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}`,
)

// 삭제 후 대시보드 목록 페이지로 이동
},
onSuccess: () => {
router.push('/dashboard')
} catch (error: unknown) {
showSuccess('대시보드가 삭제되었습니다')
},
onError: (error) => {
if (axios.isAxiosError(error)) {
const message =
error.response?.data?.message ||
'대시보드 삭제 중 오류가 발생했습니다.'
console.error('대시보드 삭제 오류:', message)
alert(message) // 또는 showError(message) 등으로 사용자에게 표시
showError(message)
} else {
console.error('대시보드 삭제 오류:', error)
alert('알 수 없는 오류가 발생했습니다.')
showError('알 수 없는 오류가 발생했습니다.')
}
} finally {
setIsDeleting(false)
}
},
})

// sonner로 삭제 확인 토스트 구현
function handleDelete() {
toast('대시보드를 삭제하시겠습니까?', {
description: '삭제 후 되돌릴 수 없습니다.',
action: {
label: '삭제하기',
onClick: () => mutation.mutate(),
},
})
}

return (
<button
onClick={handleDelete}
disabled={isDeleting}
// isLoading -> isPending으로 수정됨
disabled={mutation.isPending}
className={`Text-black my-8 rounded-8 font-semibold transition-opacity ${
isDeleting ? 'cursor-not-allowed opacity-50' : 'hover:opacity-90'
mutation.isPending
? 'cursor-not-allowed opacity-50'
: 'hover:opacity-90'
}`}
>
대시보드 삭제하기
Expand Down
170 changes: 20 additions & 150 deletions src/app/features/dashboard/components/edit/EditInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,165 +1,35 @@
'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'
import DashboardForm from '@components/dashboard/DashboardForm'
import { useDashboardForm } from '@hooks/useDashboardForm'
import React from 'react'

export default function EditInfo() {
const router = useRouter()
const { selectedDashboard, setSelectedDashboard } =
useSelectedDashboardStore()
const queryClient = useQueryClient()

const [formData, setFormData] = useState<CreateDashboardRequest>({
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<HTMLInputElement>) => {
const { name, value } = e.target
setFormData((prev) => ({
...prev,
[name]: value,
}))
}

// 색상 선택 핸들러
const handleColorSelect = (color: string) => {
setFormData((prev) => ({ ...prev, color }))
}

// 제출 핸들러
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()

if (!formData.title || !formData.color) return

try {
setIsSubmitting(true)

if (!process.env.NEXT_PUBLIC_TEAM_ID || !selectedDashboard?.id) {
throw new Error('필수 정보가 누락되었습니다.')
}

const response = await api.put(
`/${process.env.NEXT_PUBLIC_TEAM_ID}/dashboards/${selectedDashboard.id}`,
formData,
)

const data = response.data

// 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)
}
}
const {
formData,
isSubmitting,
handleChange,
handleColorSelect,
handleSubmit,
selectedDashboard,
} = useDashboardForm('edit')

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

<form onSubmit={handleSubmit}>
{/* 제목 입력 */}
<div className="mb-16">
<label htmlFor="title" className="Text-black mb-8 block text-16">
대시보드 이름
</label>
<input
type="text"
id="title"
name="title"
value={formData.title}
onChange={handleChange}
placeholder="대시보드 이름을 입력해주세요."
className="Border-section w-512 rounded-8 px-12 py-10 text-16 outline-none"
required
/>
</div>

{/* 색상 선택 */}
<div className="mb-30">
<div className="flex gap-8">
{DASHBOARD_COLORS.map((color) => (
<button
key={color}
type="button"
onClick={() => handleColorSelect(color)}
className="relative flex size-30 items-center justify-center rounded-full"
style={{ backgroundColor: color }}
aria-label={`색상 ${color}`}
>
{/* 선택된 색상에 체크 표시 */}
{formData.color === color && (
<div className="relative size-24 items-center justify-center">
<Image
src="/images/check.svg"
alt="check"
fill
className="object-contain"
/>
</div>
)}
</button>
))}
</div>
</div>

{/* 하단 버튼 */}
<div>
<button
type="submit"
disabled={!formData.title || !formData.color || isSubmitting}
className={`BG-violet h-48 w-512 rounded-8 px-16 py-10 text-16 font-semibold text-white transition-opacity ${
!formData.title || !formData.color || isSubmitting
? 'cursor-not-allowed opacity-50'
: 'hover:opacity-90'
}`}
>
변경
</button>
</div>
</form>
<DashboardForm
formData={formData}
onChange={handleChange}
onColorSelect={handleColorSelect}
onSubmit={handleSubmit}
isSubmitting={isSubmitting}
submitText="변경"
submitButtonWidth="w-516"
/>
</div>
</div>
)
Expand Down
Loading
Loading