-
Notifications
You must be signed in to change notification settings - Fork 2
✨ feat: 사이드바 대시보드 생성 모달 구현 #50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
87c31a4
c562024
8949cf9
add715f
2dbfac5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| 'use client' | ||
|
|
||
| import Image from 'next/image' | ||
| import { useRouter } from 'next/navigation' | ||
| import React, { useEffect, useState } from 'react' | ||
|
|
||
| import api from '@/app/shared/lib/axios' | ||
| import { useModalStore } from '@/app/shared/store/useModalStore' | ||
| import { CreateDashboardRequest } from '@/app/shared/types/dashboard' | ||
|
|
||
| const DASHBOARD_COLORS = ['#10B981', '#8B5CF6', '#F59E0B', '#3B82F6', '#EC4899'] | ||
|
|
||
| export default function CreateDashboardModal() { | ||
| const router = useRouter() | ||
| const { createDashboardModalOpen, closeCreateDashboardModal } = | ||
| useModalStore() | ||
|
|
||
| const [formData, setFormData] = useState<CreateDashboardRequest>({ | ||
| title: '', | ||
| color: DASHBOARD_COLORS[0], | ||
| }) | ||
|
|
||
| const [isSubmitting, setIsSubmitting] = useState(false) | ||
|
|
||
| useEffect(() => { | ||
| if (!createDashboardModalOpen) { | ||
| setFormData({ title: '', color: DASHBOARD_COLORS[0] }) | ||
| setIsSubmitting(false) | ||
| } | ||
| }, [createDashboardModalOpen]) | ||
|
|
||
| if (!createDashboardModalOpen) { | ||
| return null | ||
| } | ||
|
|
||
| const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => { | ||
| e.preventDefault() | ||
|
|
||
| if (!formData.title || !formData.color) { | ||
| return | ||
| } | ||
| try { | ||
| setIsSubmitting(true) | ||
|
|
||
| const response = await api.post(`/dashboards`, formData) | ||
|
|
||
| const data = response.data | ||
|
|
||
| // 성공 시 대시보드 상세 페이지로 이동 | ||
| router.push(`/dashboard/${data.id}`) | ||
| closeCreateDashboardModal() | ||
| } catch (error) { | ||
| console.error('대시보드 생성 오류:', error) | ||
| } finally { | ||
| setIsSubmitting(false) | ||
| } | ||
| } | ||
|
|
||
| // 입력값 변경 처리 | ||
| 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 handleBackdropClick = (e: React.MouseEvent<HTMLDivElement>) => { | ||
| if (e.target === e.currentTarget) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. e.target이랑 e.currentTarget이 각각 뭘 가리키고 있는건가요?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
현재 구조에서
모달 외부/배경 클릭 시 이 방식으로 모달 외부 클릭 시에만 모달을 닫고, 모달 내부 클릭 시에는 닫히지 않도록 구현했습니다. |
||
| closeCreateDashboardModal() | ||
| } | ||
| } | ||
|
|
||
| return ( | ||
| // 모달 백드롭 | ||
| <div | ||
| className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50" | ||
| onClick={handleBackdropClick} | ||
| > | ||
| {/* 모달 컨테이너 */} | ||
| <div className="BG-white h-344 w-584 rounded-16 p-32"> | ||
| <h2 className="Text-black mb-24 text-24 font-bold">새로운 대시보드</h2> | ||
|
|
||
| <form onSubmit={handleSubmit}> | ||
| {/* 제목 입력 */} | ||
| <div className="mb-24"> | ||
| <label htmlFor="title" className="Text-black mb-8 block text-18"> | ||
| 대시보드 이름 | ||
| </label> | ||
| <input | ||
| type="text" | ||
| id="title" | ||
| name="title" | ||
| value={formData.title} | ||
| onChange={handleChange} | ||
| placeholder="대시보드 이름을 입력해주세요." | ||
| className="Border-section w-full rounded-8 px-12 py-10 text-16 outline-none" | ||
| required | ||
| /> | ||
| </div> | ||
|
|
||
| {/* 색상 선택 */} | ||
| <div className="mb-32"> | ||
| <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 className="flex justify-end gap-10"> | ||
| <button | ||
| type="button" | ||
| onClick={closeCreateDashboardModal} | ||
| className="Border-btn Text-black h-54 w-256 rounded-8 px-16 py-10 text-16 font-semibold" | ||
| > | ||
| 취소 | ||
| </button> | ||
| <button | ||
| type="submit" | ||
| disabled={!formData.title || !formData.color || isSubmitting} | ||
| className={`BG-violet h-54 w-256 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> | ||
| </div> | ||
| </div> | ||
| ) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { create } from 'zustand' | ||
|
|
||
| import { ModalState } from '../types/dashboard' | ||
|
|
||
| export const useModalStore = create<ModalState>((set) => ({ | ||
| // 초기 상태 | ||
| createDashboardModalOpen: false, | ||
|
|
||
| // 모달 열기 | ||
| openCreateDashboardModal: () => set({ createDashboardModalOpen: true }), | ||
|
|
||
| // 모달 닫기 | ||
| closeCreateDashboardModal: () => set({ createDashboardModalOpen: false }), | ||
| })) | ||
|
Comment on lines
+1
to
+14
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Zustand로 모달 상태 관리 해주셨군용!! |
||
Uh oh!
There was an error while loading. Please reload this page.