From 1d86c6b8413c441ef1716ab31643f892c1ba629e Mon Sep 17 00:00:00 2001 From: caadiq Date: Sun, 11 Jan 2026 12:12:46 +0900 Subject: [PATCH] =?UTF-8?q?Admin=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=20?= =?UTF-8?q?AdminLayout=20=EC=A0=81=EC=9A=A9=ED=95=98=EC=97=AC=20=ED=97=A4?= =?UTF-8?q?=EB=8D=94=20=EA=B3=A0=EC=A0=95=20+=20=EB=B3=B8=EB=AC=B8=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EA=B5=AC=EC=A1=B0=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AdminLayout.jsx 컴포넌트 생성 (헤더 고정 + overflow-y-auto) - AdminDashboard, AdminMembers, AdminMemberEdit에 적용 - AdminAlbums, AdminAlbumForm, AdminAlbumPhotos에 적용 - AdminScheduleCategory, AdminScheduleBots, AdminScheduleForm에 적용 - AdminSchedule은 내부 스크롤 처리로 자동 감지하여 제외 --- frontend/src/components/admin/AdminLayout.jsx | 25 ++++ frontend/src/components/pc/Layout.jsx | 19 ++- .../src/pages/pc/admin/AdminAlbumForm.jsx | 13 +- .../src/pages/pc/admin/AdminAlbumPhotos.jsx | 24 ++-- frontend/src/pages/pc/admin/AdminAlbums.jsx | 14 +- .../src/pages/pc/admin/AdminDashboard.jsx | 13 +- .../src/pages/pc/admin/AdminMemberEdit.jsx | 13 +- frontend/src/pages/pc/admin/AdminMembers.jsx | 13 +- .../src/pages/pc/admin/AdminScheduleBots.jsx | 13 +- .../pages/pc/admin/AdminScheduleCategory.jsx | 21 ++- .../src/pages/pc/admin/AdminScheduleForm.jsx | 13 +- frontend/src/pages/pc/public/Home.jsx | 131 +++++++++++------- frontend/src/pc.css | 7 +- 13 files changed, 178 insertions(+), 141 deletions(-) create mode 100644 frontend/src/components/admin/AdminLayout.jsx diff --git a/frontend/src/components/admin/AdminLayout.jsx b/frontend/src/components/admin/AdminLayout.jsx new file mode 100644 index 0000000..1659916 --- /dev/null +++ b/frontend/src/components/admin/AdminLayout.jsx @@ -0,0 +1,25 @@ +/** + * AdminLayout 컴포넌트 + * 모든 Admin 페이지에서 공통으로 사용하는 레이아웃 + * 헤더 고정 + 본문 스크롤 구조 + */ +import { useLocation } from 'react-router-dom'; +import AdminHeader from './AdminHeader'; + +function AdminLayout({ user, children }) { + const location = useLocation(); + + // 일정 관리 페이지는 내부 스크롤 처리 + const isSchedulePage = location.pathname.includes('/admin/schedules'); + + return ( +
+ +
+ {children} +
+
+ ); +} + +export default AdminLayout; diff --git a/frontend/src/components/pc/Layout.jsx b/frontend/src/components/pc/Layout.jsx index 38bb587..d35ad38 100644 --- a/frontend/src/components/pc/Layout.jsx +++ b/frontend/src/components/pc/Layout.jsx @@ -5,14 +5,23 @@ import '../../pc.css'; function Layout({ children }) { const location = useLocation(); - // 일정 페이지에서는 Footer 숨김 (화면 고정 레이아웃) - const hideFooter = location.pathname === '/schedule'; + + // Footer 숨김 페이지 (화면 고정 레이아웃) + const hideFooterPages = ['/schedule', '/members', '/album']; + const hideFooter = hideFooterPages.some(path => + location.pathname === path || location.pathname.startsWith(path + '/') + ); + + // 일정 페이지에서는 스크롤바도 숨김 (내부에서 자체 스크롤 처리) + const isSchedulePage = location.pathname === '/schedule'; return ( -
+
-
{children}
- {!hideFooter &&
} +
+ {children} + {!hideFooter &&
} +
); } diff --git a/frontend/src/pages/pc/admin/AdminAlbumForm.jsx b/frontend/src/pages/pc/admin/AdminAlbumForm.jsx index fa564b4..38d9821 100644 --- a/frontend/src/pages/pc/admin/AdminAlbumForm.jsx +++ b/frontend/src/pages/pc/admin/AdminAlbumForm.jsx @@ -7,7 +7,7 @@ import { } from 'lucide-react'; import Toast from '../../../components/Toast'; import CustomDatePicker from '../../../components/admin/CustomDatePicker'; -import AdminHeader from '../../../components/admin/AdminHeader'; +import AdminLayout from '../../../components/admin/AdminLayout'; import useToast from '../../../hooks/useToast'; // 커스텀 드롭다운 컴포넌트 @@ -260,15 +260,12 @@ function AdminAlbumForm() { const albumTypes = ['정규', '미니', '싱글']; return ( -
+ {/* Toast */} setToast(null)} /> - - {/* 헤더 */} - {/* 메인 콘텐츠 */} -
+
{/* 브레드크럼 */} )} -
-
+
+ ); } diff --git a/frontend/src/pages/pc/admin/AdminAlbumPhotos.jsx b/frontend/src/pages/pc/admin/AdminAlbumPhotos.jsx index c3bad33..2c88c2d 100644 --- a/frontend/src/pages/pc/admin/AdminAlbumPhotos.jsx +++ b/frontend/src/pages/pc/admin/AdminAlbumPhotos.jsx @@ -8,7 +8,7 @@ import { Tag, FolderOpen, Save } from 'lucide-react'; import Toast from '../../../components/Toast'; -import AdminHeader from '../../../components/admin/AdminHeader'; +import AdminLayout from '../../../components/admin/AdminLayout'; import ConfirmDialog from '../../../components/admin/ConfirmDialog'; import useToast from '../../../hooks/useToast'; import * as authApi from '../../../api/admin/auth'; @@ -513,20 +513,17 @@ function AdminAlbumPhotos() { if (loading) { return ( -
- {/* 헤더 */} - - + {/* 로딩 스피너 */} -
+
-
-
+ + ); } return ( -
+ {/* Toast */} setToast(null)} /> @@ -588,11 +585,8 @@ function AdminAlbumPhotos() { )} - {/* 헤더 */} - - {/* 메인 콘텐츠 */} -
+
{/* 브레드크럼 */} -
-
+ + ); } diff --git a/frontend/src/pages/pc/admin/AdminAlbums.jsx b/frontend/src/pages/pc/admin/AdminAlbums.jsx index b089cde..0b0803d 100644 --- a/frontend/src/pages/pc/admin/AdminAlbums.jsx +++ b/frontend/src/pages/pc/admin/AdminAlbums.jsx @@ -7,13 +7,14 @@ import { } from 'lucide-react'; import Toast from '../../../components/Toast'; import Tooltip from '../../../components/Tooltip'; -import AdminHeader from '../../../components/admin/AdminHeader'; +import AdminLayout from '../../../components/admin/AdminLayout'; import ConfirmDialog from '../../../components/admin/ConfirmDialog'; import useToast from '../../../hooks/useToast'; import * as authApi from '../../../api/admin/auth'; import { getAlbums } from '../../../api/public/albums'; import * as albumsApi from '../../../api/admin/albums'; + function AdminAlbums() { const navigate = useNavigate(); const [albums, setAlbums] = useState([]); @@ -76,7 +77,7 @@ function AdminAlbums() { ); return ( -
+ {/* Toast */} setToast(null)} /> @@ -96,11 +97,8 @@ function AdminAlbums() { loading={deleting} /> - {/* 헤더 */} - - {/* 메인 콘텐츠 */} -
+
{/* 브레드크럼 */}
@@ -234,8 +232,8 @@ function AdminAlbums() { )}
)} -
-
+ + ); } diff --git a/frontend/src/pages/pc/admin/AdminDashboard.jsx b/frontend/src/pages/pc/admin/AdminDashboard.jsx index 5c885d6..2bab25b 100644 --- a/frontend/src/pages/pc/admin/AdminDashboard.jsx +++ b/frontend/src/pages/pc/admin/AdminDashboard.jsx @@ -5,7 +5,7 @@ import { Disc3, Calendar, Users, Home, ChevronRight } from 'lucide-react'; -import AdminHeader from '../../../components/admin/AdminHeader'; +import AdminLayout from '../../../components/admin/AdminLayout'; import * as authApi from '../../../api/admin/auth'; import { getMembers } from '../../../api/public/members'; import { getAlbums, getAlbum } from '../../../api/public/albums'; @@ -134,12 +134,9 @@ function AdminDashboard() { ]; return ( -
- {/* 헤더 */} - - + {/* 메인 콘텐츠 */} -
+
{/* 브레드크럼 */}
@@ -198,8 +195,8 @@ function AdminDashboard() {
- - + + ); } diff --git a/frontend/src/pages/pc/admin/AdminMemberEdit.jsx b/frontend/src/pages/pc/admin/AdminMemberEdit.jsx index 779a430..1da9ffe 100644 --- a/frontend/src/pages/pc/admin/AdminMemberEdit.jsx +++ b/frontend/src/pages/pc/admin/AdminMemberEdit.jsx @@ -7,7 +7,7 @@ import { } from 'lucide-react'; import Toast from '../../../components/Toast'; import CustomDatePicker from '../../../components/admin/CustomDatePicker'; -import AdminHeader from '../../../components/admin/AdminHeader'; +import AdminLayout from '../../../components/admin/AdminLayout'; import useToast from '../../../hooks/useToast'; import * as authApi from '../../../api/admin/auth'; import * as membersApi from '../../../api/admin/members'; @@ -99,15 +99,12 @@ function AdminMemberEdit() { }; return ( -
+ {/* Toast */} setToast(null)} /> - {/* 헤더 */} - - {/* 메인 콘텐츠 */} -
+
{/* 브레드크럼 */}
@@ -300,8 +297,8 @@ function AdminMemberEdit() {
)} -
-
+ + ); } diff --git a/frontend/src/pages/pc/admin/AdminMembers.jsx b/frontend/src/pages/pc/admin/AdminMembers.jsx index 15f6fbc..08718fd 100644 --- a/frontend/src/pages/pc/admin/AdminMembers.jsx +++ b/frontend/src/pages/pc/admin/AdminMembers.jsx @@ -6,7 +6,7 @@ import { Home, ChevronRight, Users, User } from 'lucide-react'; import Toast from '../../../components/Toast'; -import AdminHeader from '../../../components/admin/AdminHeader'; +import AdminLayout from '../../../components/admin/AdminLayout'; import useToast from '../../../hooks/useToast'; function AdminMembers() { @@ -93,15 +93,12 @@ function AdminMembers() { ); return ( -
+ {/* Toast */} setToast(null)} /> - {/* 헤더 */} - - {/* 메인 콘텐츠 */} -
+
{/* 브레드크럼 */}
@@ -169,8 +166,8 @@ function AdminMembers() { )}
)} -
-
+ + ); } diff --git a/frontend/src/pages/pc/admin/AdminScheduleBots.jsx b/frontend/src/pages/pc/admin/AdminScheduleBots.jsx index 9bc1b63..b3c4b63 100644 --- a/frontend/src/pages/pc/admin/AdminScheduleBots.jsx +++ b/frontend/src/pages/pc/admin/AdminScheduleBots.jsx @@ -7,7 +7,7 @@ import { } from 'lucide-react'; import Toast from '../../../components/Toast'; import Tooltip from '../../../components/Tooltip'; -import AdminHeader from '../../../components/admin/AdminHeader'; +import AdminLayout from '../../../components/admin/AdminLayout'; import useToast from '../../../hooks/useToast'; import * as botsApi from '../../../api/admin/bots'; @@ -214,14 +214,11 @@ function AdminScheduleBots() { }; return ( -
+ setToast(null)} /> - {/* 헤더 */} - - {/* 메인 콘텐츠 */} -
+
{/* 브레드크럼 */}
@@ -451,8 +448,8 @@ function AdminScheduleBots() { )}
-
-
+ + ); } diff --git a/frontend/src/pages/pc/admin/AdminScheduleCategory.jsx b/frontend/src/pages/pc/admin/AdminScheduleCategory.jsx index 9698372..ded9802 100644 --- a/frontend/src/pages/pc/admin/AdminScheduleCategory.jsx +++ b/frontend/src/pages/pc/admin/AdminScheduleCategory.jsx @@ -4,7 +4,7 @@ import { motion, AnimatePresence, Reorder } from 'framer-motion'; import { Home, ChevronRight, Plus, Edit3, Trash2, GripVertical, X } from 'lucide-react'; import { HexColorPicker } from 'react-colorful'; import Toast from '../../../components/Toast'; -import AdminHeader from '../../../components/admin/AdminHeader'; +import AdminLayout from '../../../components/admin/AdminLayout'; import ConfirmDialog from '../../../components/admin/ConfirmDialog'; import useToast from '../../../hooks/useToast'; import * as authApi from '../../../api/admin/auth'; @@ -174,21 +174,20 @@ function AdminScheduleCategory() { if (loading) { return ( -
-
-
+ +
+
+
+
); } return ( -
+ setToast(null)} /> - {/* 헤더 */} - - {/* 메인 콘텐츠 */} -
+
{/* 브레드크럼 */}
@@ -279,7 +278,7 @@ function AdminScheduleCategory() { )}
-
+
{/* 추가/수정 모달 */} @@ -460,7 +459,7 @@ function AdminScheduleCategory() { } /> - + ); } diff --git a/frontend/src/pages/pc/admin/AdminScheduleForm.jsx b/frontend/src/pages/pc/admin/AdminScheduleForm.jsx index d91a3d4..e747dea 100644 --- a/frontend/src/pages/pc/admin/AdminScheduleForm.jsx +++ b/frontend/src/pages/pc/admin/AdminScheduleForm.jsx @@ -26,7 +26,7 @@ import Toast from "../../../components/Toast"; import Lightbox from "../../../components/common/Lightbox"; import CustomDatePicker from "../../../components/admin/CustomDatePicker"; import CustomTimePicker from "../../../components/admin/CustomTimePicker"; -import AdminHeader from "../../../components/admin/AdminHeader"; +import AdminLayout from "../../../components/admin/AdminLayout"; import ConfirmDialog from "../../../components/admin/ConfirmDialog"; import useToast from "../../../hooks/useToast"; import * as authApi from "../../../api/admin/auth"; @@ -508,7 +508,7 @@ function AdminScheduleForm() { }; return ( -
+ setToast(null)} /> {/* 삭제 확인 다이얼로그 */} @@ -664,11 +664,8 @@ function AdminScheduleForm() { onIndexChange={setLightboxIndex} /> - {/* 헤더 */} - - {/* 메인 콘텐츠 */} -
+
{/* 브레드크럼 */}
-
-
+ + ); } diff --git a/frontend/src/pages/pc/public/Home.jsx b/frontend/src/pages/pc/public/Home.jsx index 56a27ee..c40a54b 100644 --- a/frontend/src/pages/pc/public/Home.jsx +++ b/frontend/src/pages/pc/public/Home.jsx @@ -61,44 +61,35 @@ function Home() { {/* 그룹 통계 섹션 */}
-
- -

2018.01.24

-

데뷔일

-
- -

D+{(Math.floor((new Date() - new Date('2018-01-24')) / (1000 * 60 * 60 * 24)) + 1).toLocaleString()}

-

D+Day

-
- -

5

-

멤버 수

-
- -

flover

-

팬덤명

-
-
+ + {[ + { value: "2018.01.24", label: "데뷔일" }, + { value: `D+${(Math.floor((new Date() - new Date('2018-01-24')) / (1000 * 60 * 60 * 24)) + 1).toLocaleString()}`, label: "D+Day" }, + { value: "5", label: "멤버 수" }, + { value: "flover", label: "팬덤명" } + ].map((stat, index) => ( + +

{stat.value}

+

{stat.label}

+
+ ))} +
@@ -115,21 +106,26 @@ function Home() { {members.filter(m => !m.is_former).map((member, index) => ( -
+ {/* 이미지 컨테이너 */} +
{member.name}
-
-

{member.name}

-

{member.position?.split(',')[0]}

+ + {/* 그라데이션 오버레이 */} +
+ + {/* 멤버 정보 */} +
+

{member.name}

))} @@ -152,9 +148,27 @@ function Home() {

예정된 일정이 없습니다

) : ( -
- {upcomingSchedules.map((schedule, index) => { + + {upcomingSchedules.map((schedule) => { const scheduleDate = new Date(schedule.date); + const today = new Date(); + const currentYear = today.getFullYear(); + const currentMonth = today.getMonth(); + + const scheduleYear = scheduleDate.getFullYear(); + const scheduleMonth = scheduleDate.getMonth(); + const isCurrentYear = scheduleYear === currentYear; + const isCurrentMonth = isCurrentYear && scheduleMonth === currentMonth; + const day = scheduleDate.getDate(); const weekdays = ['일', '월', '화', '수', '목', '금', '토']; const weekday = weekdays[scheduleDate.getDay()]; @@ -166,13 +180,26 @@ function Home() { return ( {/* 날짜 영역 - primary 색상 고정 */}
+ {/* 현재 년도가 아니면 년.월 표시 */} + {!isCurrentYear && ( + + {scheduleYear}.{scheduleMonth + 1} + + )} + {/* 현재 달이 아니면 월 표시 (현재 년도일 때) */} + {isCurrentYear && !isCurrentMonth && ( + + {scheduleMonth + 1}월 + + )} {day} {weekday}
@@ -219,7 +246,7 @@ function Home() {
); })} -
+ )} diff --git a/frontend/src/pc.css b/frontend/src/pc.css index 31f49a1..3d217c9 100644 --- a/frontend/src/pc.css +++ b/frontend/src/pc.css @@ -1,11 +1,14 @@ /* PC 전용 스타일 - body.is-pc 클래스가 있을 때만 적용 */ -/* PC 항상 스크롤바 공간 확보 - 화면 밀림 방지 */ +/* PC에서는 body 스크롤 숨기고 내부 영역에서만 스크롤 */ +html.is-pc, body.is-pc { - overflow-y: scroll; + height: 100%; + overflow: hidden; } /* PC 최소 너비 설정 */ body.is-pc #root { min-width: 1440px; + height: 100%; }