From 7867eb8928fe6c5fd044e76b1428d4ea3b198d55 Mon Sep 17 00:00:00 2001 From: caadiq Date: Fri, 9 Jan 2026 22:42:33 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20CustomDatePicker=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - components/admin/CustomDatePicker.jsx 생성 (269줄) - AdminMemberEdit에서 중복 제거 (-237줄) - AdminAlbumForm에서 중복 제거 (-273줄) - AdminScheduleForm에서 중복 제거 (-349줄) - 현재 년도/월 표시: 테두리 제거, 글씨색만 유지 - 오늘 날짜: 배경색 제거, 글씨색만 유지 - 요일/일요일/토요일 색상 구분 추가 총 코드 감소: 약 860줄 --- .../src/components/admin/CustomDatePicker.jsx | 261 +++++++++++++ .../src/pages/pc/admin/AdminAlbumForm.jsx | 277 +------------- .../src/pages/pc/admin/AdminMemberEdit.jsx | 240 +----------- .../src/pages/pc/admin/AdminScheduleForm.jsx | 350 +----------------- 4 files changed, 266 insertions(+), 862 deletions(-) create mode 100644 frontend/src/components/admin/CustomDatePicker.jsx diff --git a/frontend/src/components/admin/CustomDatePicker.jsx b/frontend/src/components/admin/CustomDatePicker.jsx new file mode 100644 index 0000000..98abb0c --- /dev/null +++ b/frontend/src/components/admin/CustomDatePicker.jsx @@ -0,0 +1,261 @@ +/** + * 커스텀 데이트픽커 컴포넌트 + * 연/월/일 선택이 가능한 드롭다운 형태의 날짜 선택기 + */ +import { useState, useEffect, useRef } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { Calendar, ChevronLeft, ChevronRight, ChevronDown } from 'lucide-react'; + +function CustomDatePicker({ value, onChange, placeholder = '날짜 선택', showDayOfWeek = false }) { + const [isOpen, setIsOpen] = useState(false); + const [viewMode, setViewMode] = useState('days'); + const [viewDate, setViewDate] = useState(() => { + if (value) return new Date(value); + return new Date(); + }); + const ref = useRef(null); + + useEffect(() => { + const handleClickOutside = (e) => { + if (ref.current && !ref.current.contains(e.target)) { + setIsOpen(false); + setViewMode('days'); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + const year = viewDate.getFullYear(); + const month = viewDate.getMonth(); + + const firstDay = new Date(year, month, 1).getDay(); + const daysInMonth = new Date(year, month + 1, 0).getDate(); + + const days = []; + for (let i = 0; i < firstDay; i++) { + days.push(null); + } + for (let i = 1; i <= daysInMonth; i++) { + days.push(i); + } + + const startYear = Math.floor(year / 10) * 10 - 1; + const years = Array.from({ length: 12 }, (_, i) => startYear + i); + + const prevMonth = () => setViewDate(new Date(year, month - 1, 1)); + const nextMonth = () => setViewDate(new Date(year, month + 1, 1)); + const prevYearRange = () => setViewDate(new Date(year - 10, month, 1)); + const nextYearRange = () => setViewDate(new Date(year + 10, month, 1)); + + const selectDate = (day) => { + const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; + onChange(dateStr); + setIsOpen(false); + setViewMode('days'); + }; + + const selectYear = (y) => { + setViewDate(new Date(y, month, 1)); + setViewMode('months'); + }; + + const selectMonth = (m) => { + setViewDate(new Date(year, m, 1)); + setViewMode('days'); + }; + + // 날짜 표시 포맷 (요일 포함 옵션) + const formatDisplayDate = (dateStr) => { + if (!dateStr) return ''; + const [y, m, d] = dateStr.split('-'); + if (showDayOfWeek) { + const dayNames = ['일', '월', '화', '수', '목', '금', '토']; + const date = new Date(parseInt(y), parseInt(m) - 1, parseInt(d)); + const dayOfWeek = dayNames[date.getDay()]; + return `${y}년 ${parseInt(m)}월 ${parseInt(d)}일 (${dayOfWeek})`; + } + return `${y}년 ${parseInt(m)}월 ${parseInt(d)}일`; + }; + + const isSelected = (day) => { + if (!value || !day) return false; + const [y, m, d] = value.split('-'); + return parseInt(y) === year && parseInt(m) === month + 1 && parseInt(d) === day; + }; + + const isToday = (day) => { + if (!day) return false; + const today = new Date(); + return today.getFullYear() === year && today.getMonth() === month && today.getDate() === day; + }; + + const isCurrentYear = (y) => new Date().getFullYear() === y; + const isCurrentMonth = (m) => { + const today = new Date(); + return today.getFullYear() === year && today.getMonth() === m; + }; + + const monthNames = ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월']; + + return ( +
+ + + + {isOpen && ( + +
+ + + +
+ + + {viewMode === 'years' && ( + +
년도
+
+ {years.map((y) => ( + + ))} +
+
+
+ {monthNames.map((m, i) => ( + + ))} +
+
+ )} + + {viewMode === 'months' && ( + +
월 선택
+
+ {monthNames.map((m, i) => ( + + ))} +
+
+ )} + + {viewMode === 'days' && ( + +
+ {['일', '월', '화', '수', '목', '금', '토'].map((d, i) => ( +
+ {d} +
+ ))} +
+
+ {days.map((day, i) => { + const dayOfWeek = i % 7; + return ( + + ); + })} +
+
+ )} +
+
+ )} +
+
+ ); +} + +export default CustomDatePicker; diff --git a/frontend/src/pages/pc/admin/AdminAlbumForm.jsx b/frontend/src/pages/pc/admin/AdminAlbumForm.jsx index 3131f83..e19ddff 100644 --- a/frontend/src/pages/pc/admin/AdminAlbumForm.jsx +++ b/frontend/src/pages/pc/admin/AdminAlbumForm.jsx @@ -3,9 +3,10 @@ import { useNavigate, useParams, Link } from 'react-router-dom'; import { motion, AnimatePresence } from 'framer-motion'; import { Save, Home, ChevronRight, LogOut, Music, Trash2, Plus, Image, Star, - ChevronDown, ChevronLeft, Calendar + ChevronDown } from 'lucide-react'; import Toast from '../../../components/Toast'; +import CustomDatePicker from '../../../components/admin/CustomDatePicker'; // 커스텀 드롭다운 컴포넌트 function CustomSelect({ value, onChange, options, placeholder }) { @@ -71,280 +72,6 @@ function CustomSelect({ value, onChange, options, placeholder }) { ); } -// 커스텀 데이트픽커 컴포넌트 -function CustomDatePicker({ value, onChange }) { - const [isOpen, setIsOpen] = useState(false); - const [viewMode, setViewMode] = useState('days'); // 'days' | 'months' | 'years' - const [viewDate, setViewDate] = useState(() => { - if (value) return new Date(value); - return new Date(); - }); - const ref = useRef(null); - - useEffect(() => { - const handleClickOutside = (e) => { - if (ref.current && !ref.current.contains(e.target)) { - setIsOpen(false); - setViewMode('days'); - } - }; - document.addEventListener('mousedown', handleClickOutside); - return () => document.removeEventListener('mousedown', handleClickOutside); - }, []); - - const year = viewDate.getFullYear(); - const month = viewDate.getMonth(); - - const firstDay = new Date(year, month, 1).getDay(); - const daysInMonth = new Date(year, month + 1, 0).getDate(); - - const days = []; - for (let i = 0; i < firstDay; i++) { - days.push(null); - } - for (let i = 1; i <= daysInMonth; i++) { - days.push(i); - } - - // 년도 범위 (현재 년도 기준 -10 ~ +10) - const startYear = Math.floor(year / 10) * 10 - 1; - const years = Array.from({ length: 12 }, (_, i) => startYear + i); - - const prevMonth = () => setViewDate(new Date(year, month - 1, 1)); - const nextMonth = () => setViewDate(new Date(year, month + 1, 1)); - const prevYearRange = () => setViewDate(new Date(year - 10, month, 1)); - const nextYearRange = () => setViewDate(new Date(year + 10, month, 1)); - - const selectDate = (day) => { - const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; - onChange(dateStr); - setIsOpen(false); - setViewMode('days'); - }; - - const selectYear = (y) => { - setViewDate(new Date(y, month, 1)); - setViewMode('months'); - }; - - const selectMonth = (m) => { - setViewDate(new Date(year, m, 1)); - setViewMode('days'); - }; - - const formatDisplayDate = (dateStr) => { - if (!dateStr) return ''; - const [y, m, d] = dateStr.split('-'); - return `${y}년 ${parseInt(m)}월 ${parseInt(d)}일`; - }; - - const isSelected = (day) => { - if (!value || !day) return false; - const [y, m, d] = value.split('-'); - return parseInt(y) === year && parseInt(m) === month + 1 && parseInt(d) === day; - }; - - const isToday = (day) => { - if (!day) return false; - const today = new Date(); - return today.getFullYear() === year && today.getMonth() === month && today.getDate() === day; - }; - - const isCurrentYear = (y) => { - return new Date().getFullYear() === y; - }; - - const isCurrentMonth = (m) => { - const today = new Date(); - return today.getFullYear() === year && today.getMonth() === m; - }; - - const months = ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월']; - - return ( -
- - - - {isOpen && ( - - {/* 헤더 */} -
- - - -
- - - {viewMode === 'years' && ( - - {/* 년도 라벨 */} -
년도
- - {/* 년도 그리드 */} -
- {years.map((y) => ( - - ))} -
- - {/* 월 라벨 */} -
- - {/* 월 그리드 */} -
- {months.map((m, i) => ( - - ))} -
-
- )} - - {viewMode === 'months' && ( - - {/* 월 라벨 */} -
월 선택
- - {/* 월 그리드 */} -
- {months.map((m, i) => ( - - ))} -
-
- )} - - {viewMode === 'days' && ( - - {/* 요일 */} -
- {['일', '월', '화', '수', '목', '금', '토'].map((d, i) => ( -
- {d} -
- ))} -
- - {/* 날짜 */} -
- {days.map((day, i) => ( - - ))} -
-
- )} -
-
- )} -
-
- ); -} - function AdminAlbumForm() { const navigate = useNavigate(); const { id } = useParams(); diff --git a/frontend/src/pages/pc/admin/AdminMemberEdit.jsx b/frontend/src/pages/pc/admin/AdminMemberEdit.jsx index f26e3d1..a98a34d 100644 --- a/frontend/src/pages/pc/admin/AdminMemberEdit.jsx +++ b/frontend/src/pages/pc/admin/AdminMemberEdit.jsx @@ -3,249 +3,13 @@ import { useNavigate, useParams, Link } from 'react-router-dom'; import { motion, AnimatePresence } from 'framer-motion'; import { Save, Upload, LogOut, - Home, ChevronRight, ChevronLeft, ChevronDown, User, Instagram, Calendar, Briefcase + Home, ChevronRight, User, Instagram, Calendar, Briefcase } from 'lucide-react'; import Toast from '../../../components/Toast'; +import CustomDatePicker from '../../../components/admin/CustomDatePicker'; import * as authApi from '../../../api/admin/auth'; import * as membersApi from '../../../api/admin/members'; -// 커스텀 데이트픽커 컴포넌트 -function CustomDatePicker({ value, onChange }) { - const [isOpen, setIsOpen] = useState(false); - const [viewMode, setViewMode] = useState('days'); - const [viewDate, setViewDate] = useState(() => { - if (value) return new Date(value); - return new Date(); - }); - const ref = useRef(null); - - useEffect(() => { - const handleClickOutside = (e) => { - if (ref.current && !ref.current.contains(e.target)) { - setIsOpen(false); - setViewMode('days'); - } - }; - document.addEventListener('mousedown', handleClickOutside); - return () => document.removeEventListener('mousedown', handleClickOutside); - }, []); - - const year = viewDate.getFullYear(); - const month = viewDate.getMonth(); - - const firstDay = new Date(year, month, 1).getDay(); - const daysInMonth = new Date(year, month + 1, 0).getDate(); - - const days = []; - for (let i = 0; i < firstDay; i++) { - days.push(null); - } - for (let i = 1; i <= daysInMonth; i++) { - days.push(i); - } - - const startYear = Math.floor(year / 10) * 10 - 1; - const years = Array.from({ length: 12 }, (_, i) => startYear + i); - - const prevMonth = () => setViewDate(new Date(year, month - 1, 1)); - const nextMonth = () => setViewDate(new Date(year, month + 1, 1)); - const prevYearRange = () => setViewDate(new Date(year - 10, month, 1)); - const nextYearRange = () => setViewDate(new Date(year + 10, month, 1)); - - const selectDate = (day) => { - const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; - onChange(dateStr); - setIsOpen(false); - setViewMode('days'); - }; - - const selectYear = (y) => { - setViewDate(new Date(y, month, 1)); - setViewMode('months'); - }; - - const selectMonth = (m) => { - setViewDate(new Date(year, m, 1)); - setViewMode('days'); - }; - - const formatDisplayDate = (dateStr) => { - if (!dateStr) return ''; - const [y, m, d] = dateStr.split('-'); - return `${y}년 ${parseInt(m)}월 ${parseInt(d)}일`; - }; - - const isSelected = (day) => { - if (!value || !day) return false; - const [y, m, d] = value.split('-'); - return parseInt(y) === year && parseInt(m) === month + 1 && parseInt(d) === day; - }; - - const isToday = (day) => { - if (!day) return false; - const today = new Date(); - return today.getFullYear() === year && today.getMonth() === month && today.getDate() === day; - }; - - const isCurrentYear = (y) => new Date().getFullYear() === y; - const isCurrentMonth = (m) => { - const today = new Date(); - return today.getFullYear() === year && today.getMonth() === m; - }; - - const months = ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월']; - - return ( -
- - - - {isOpen && ( - -
- - - -
- - - {viewMode === 'years' && ( - -
년도
-
- {years.map((y) => ( - - ))} -
-
-
- {months.map((m, i) => ( - - ))} -
-
- )} - - {viewMode === 'months' && ( - -
월 선택
-
- {months.map((m, i) => ( - - ))} -
-
- )} - - {viewMode === 'days' && ( - -
- {['일', '월', '화', '수', '목', '금', '토'].map((d, i) => ( -
- {d} -
- ))} -
-
- {days.map((day, i) => ( - - ))} -
-
- )} -
-
- )} -
-
- ); -} - - function AdminMemberEdit() { const navigate = useNavigate(); const { name } = useParams(); diff --git a/frontend/src/pages/pc/admin/AdminScheduleForm.jsx b/frontend/src/pages/pc/admin/AdminScheduleForm.jsx index b541fb0..d3131a0 100644 --- a/frontend/src/pages/pc/admin/AdminScheduleForm.jsx +++ b/frontend/src/pages/pc/admin/AdminScheduleForm.jsx @@ -26,359 +26,11 @@ import { } from "lucide-react"; import Toast from "../../../components/Toast"; import Lightbox from "../../../components/common/Lightbox"; +import CustomDatePicker from "../../../components/admin/CustomDatePicker"; import * as authApi from "../../../api/admin/auth"; import * as categoriesApi from "../../../api/admin/categories"; import * as schedulesApi from "../../../api/admin/schedules"; import { getMembers } from "../../../api/public/members"; -// 커스텀 데이트픽커 컴포넌트 (AdminMemberEdit.jsx에서 가져옴) -function CustomDatePicker({ value, onChange, placeholder = "날짜 선택" }) { - const [isOpen, setIsOpen] = useState(false); - const [viewMode, setViewMode] = useState("days"); - const [viewDate, setViewDate] = useState(() => { - if (value) return new Date(value); - return new Date(); - }); - const ref = useRef(null); - - useEffect(() => { - const handleClickOutside = (e) => { - if (ref.current && !ref.current.contains(e.target)) { - setIsOpen(false); - setViewMode("days"); - } - }; - document.addEventListener("mousedown", handleClickOutside); - return () => document.removeEventListener("mousedown", handleClickOutside); - }, []); - - const year = viewDate.getFullYear(); - const month = viewDate.getMonth(); - - const firstDay = new Date(year, month, 1).getDay(); - const daysInMonth = new Date(year, month + 1, 0).getDate(); - - const days = []; - for (let i = 0; i < firstDay; i++) { - days.push(null); - } - for (let i = 1; i <= daysInMonth; i++) { - days.push(i); - } - - const startYear = Math.floor(year / 10) * 10 - 1; - const years = Array.from({ length: 12 }, (_, i) => startYear + i); - - const prevMonth = () => setViewDate(new Date(year, month - 1, 1)); - const nextMonth = () => setViewDate(new Date(year, month + 1, 1)); - const prevYearRange = () => setViewDate(new Date(year - 10, month, 1)); - const nextYearRange = () => setViewDate(new Date(year + 10, month, 1)); - - const selectDate = (day) => { - const dateStr = `${year}-${String(month + 1).padStart(2, "0")}-${String( - day - ).padStart(2, "0")}`; - onChange(dateStr); - setIsOpen(false); - setViewMode("days"); - }; - - const selectYear = (y) => { - setViewDate(new Date(y, month, 1)); - setViewMode("months"); - }; - - const selectMonth = (m) => { - setViewDate(new Date(year, m, 1)); - setViewMode("days"); - }; - - const formatDisplayDate = (dateStr) => { - if (!dateStr) return ""; - const [y, m, d] = dateStr.split("-"); - const days = ['일', '월', '화', '수', '목', '금', '토']; - const date = new Date(parseInt(y), parseInt(m) - 1, parseInt(d)); - const dayOfWeek = days[date.getDay()]; - return `${y}년 ${parseInt(m)}월 ${parseInt(d)}일 (${dayOfWeek})`; - }; - - const isSelected = (day) => { - if (!value || !day) return false; - const [y, m, d] = value.split("-"); - return ( - parseInt(y) === year && parseInt(m) === month + 1 && parseInt(d) === day - ); - }; - - const isToday = (day) => { - if (!day) return false; - const today = new Date(); - return ( - today.getFullYear() === year && - today.getMonth() === month && - today.getDate() === day - ); - }; - - const isCurrentYear = (y) => new Date().getFullYear() === y; - const isCurrentMonth = (m) => { - const today = new Date(); - return today.getFullYear() === year && today.getMonth() === m; - }; - - const months = [ - "1월", - "2월", - "3월", - "4월", - "5월", - "6월", - "7월", - "8월", - "9월", - "10월", - "11월", - "12월", - ]; - - return ( -
- - - - {isOpen && ( - -
- - - -
- - - {viewMode === "years" && ( - -
- 년도 -
-
- {years.map((y) => ( - - ))} -
-
- 월 -
-
- {months.map((m, i) => ( - - ))} -
-
- )} - - {viewMode === "months" && ( - -
- 월 선택 -
-
- {months.map((m, i) => ( - - ))} -
-
- )} - - {viewMode === "days" && ( - -
- {["일", "월", "화", "수", "목", "금", "토"].map((d, i) => ( -
- {d} -
- ))} -
-
- {days.map((day, i) => { - const dayOfWeek = i % 7; - return ( - - ); - })} -
-
- )} -
-
- )} -
-
- ); -} // 숫자 피커 컬럼 컴포넌트 (Vue 컴포넌트를 React로 변환) function NumberPicker({ items, value, onChange }) {