refactor: Low 우선순위 코드 품질 개선
- constants에 GROUP_INFO 상수 추가 (데뷔일, 팬덤명) - PC Home에서 멤버 수 동적 계산 (API 기반) - mobile/Layout.jsx 컴포넌트 분리 (Header.jsx, BottomNav.jsx) - 미사용 유틸리티 함수는 관리자 페이지용으로 유지 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
116d41ff07
commit
a0fc67adae
8 changed files with 122 additions and 82 deletions
|
|
@ -943,12 +943,14 @@ export function decodeHtmlEntities(text) {
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. 낮은 우선순위 (Low)
|
## 4. 낮은 우선순위 (Low) ✅ 완료
|
||||||
|
|
||||||
### 4.1 하드코딩된 값
|
### 4.1 하드코딩된 값 ✅
|
||||||
|
|
||||||
**파일**: `pages/home/pc/Home.jsx`, `pages/home/mobile/Home.jsx`
|
**파일**: `pages/home/pc/Home.jsx`, `pages/home/mobile/Home.jsx`
|
||||||
|
|
||||||
|
**상태**: ✅ 완료 - `GROUP_INFO` 상수 추가, 멤버 수는 API에서 동적 계산
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// 하드코딩된 데뷔일
|
// 하드코딩된 데뷔일
|
||||||
const debutDate = new Date('2018-01-24');
|
const debutDate = new Date('2018-01-24');
|
||||||
|
|
@ -959,51 +961,57 @@ const debutDate = new Date('2018-01-24');
|
||||||
{ value: '2018.01.24', label: '데뷔일' },
|
{ value: '2018.01.24', label: '데뷔일' },
|
||||||
```
|
```
|
||||||
|
|
||||||
**해결방법**: 상수 또는 API로 이동
|
**해결방법**: 상수 + 동적 계산
|
||||||
```javascript
|
```javascript
|
||||||
// constants/index.js에 추가
|
// constants/index.js에 추가
|
||||||
export const GROUP_INFO = {
|
export const GROUP_INFO = {
|
||||||
|
NAME: 'fromis_9',
|
||||||
|
NAME_KR: '프로미스나인',
|
||||||
DEBUT_DATE: '2018-01-24',
|
DEBUT_DATE: '2018-01-24',
|
||||||
|
DEBUT_DATE_DISPLAY: '2018.01.24',
|
||||||
FANDOM_NAME: 'flover',
|
FANDOM_NAME: 'flover',
|
||||||
};
|
};
|
||||||
|
|
||||||
// 또는 멤버 수는 API에서 동적으로 계산
|
// 멤버 수는 API에서 동적으로 계산
|
||||||
const activeMembers = members.filter(m => !m.is_former);
|
const activeMemberCount = members.filter(m => !m.is_former).length;
|
||||||
const memberCount = activeMembers.length;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 4.2 미사용 유틸리티 함수
|
### 4.2 미사용 유틸리티 함수 ✅ (유지)
|
||||||
|
|
||||||
**파일**: `utils/format.js`, `utils/schedule.js`
|
**파일**: `utils/format.js`, `utils/schedule.js`
|
||||||
|
|
||||||
|
**상태**: ✅ 검토 완료 - 관리자 페이지 마이그레이션에서 사용 예정으로 유지
|
||||||
|
|
||||||
**미사용 함수 목록**:
|
**미사용 함수 목록**:
|
||||||
- `formatViewCount()` - 검색 결과 0건
|
- `formatViewCount()` - 검색 결과 0건
|
||||||
- `formatFileSize()` - 검색 결과 0건
|
- `formatFileSize()` - 검색 결과 0건
|
||||||
- `groupSchedulesByDate()` - 검색 결과 0건 (정의부만)
|
- `groupSchedulesByDate()` - 검색 결과 0건 (정의부만)
|
||||||
- `countByCategory()` - 검색 결과 0건 (정의부만)
|
- `countByCategory()` - 검색 결과 0건 (정의부만)
|
||||||
|
|
||||||
**권장**: 관리자 페이지 마이그레이션 후 재검토, 여전히 미사용이면 삭제
|
**결정**: 관리자 페이지 마이그레이션 후 재검토, 여전히 미사용이면 삭제
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 4.3 mobile/Layout.jsx 컴포넌트 분리
|
### 4.3 mobile/Layout.jsx 컴포넌트 분리 ✅
|
||||||
|
|
||||||
**파일**: `components/mobile/Layout.jsx`
|
**파일**: `components/mobile/Layout.jsx`
|
||||||
|
|
||||||
|
**상태**: ✅ 완료 - Header.jsx, BottomNav.jsx로 분리
|
||||||
|
|
||||||
**문제**: 한 파일에 3개 컴포넌트 존재
|
**문제**: 한 파일에 3개 컴포넌트 존재
|
||||||
- `MobileHeader`
|
- `MobileHeader`
|
||||||
- `MobileBottomNav`
|
- `MobileBottomNav`
|
||||||
- `Layout`
|
- `Layout`
|
||||||
|
|
||||||
**권장**: 별도 파일로 분리
|
**해결**: 별도 파일로 분리
|
||||||
```
|
```
|
||||||
components/mobile/
|
components/mobile/
|
||||||
├── Layout.jsx
|
├── Layout.jsx # 분리된 컴포넌트 import
|
||||||
├── Header.jsx # MobileHeader
|
├── Header.jsx # MobileHeader
|
||||||
├── BottomNav.jsx # MobileBottomNav
|
├── BottomNav.jsx # MobileBottomNav
|
||||||
└── index.js
|
└── index.js # Header, BottomNav export 추가
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -277,6 +277,8 @@ function App() {
|
||||||
|
|
||||||
### Mobile 컴포넌트 (components/mobile/)
|
### Mobile 컴포넌트 (components/mobile/)
|
||||||
- [x] Layout.jsx
|
- [x] Layout.jsx
|
||||||
|
- [x] Header.jsx (MobileHeader)
|
||||||
|
- [x] BottomNav.jsx (MobileBottomNav)
|
||||||
- [x] Calendar.jsx
|
- [x] Calendar.jsx
|
||||||
- [x] ScheduleCard.jsx
|
- [x] ScheduleCard.jsx
|
||||||
- [x] ScheduleListCard.jsx
|
- [x] ScheduleListCard.jsx
|
||||||
|
|
|
||||||
45
frontend-temp/src/components/mobile/BottomNav.jsx
Normal file
45
frontend-temp/src/components/mobile/BottomNav.jsx
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { NavLink, useLocation } from 'react-router-dom';
|
||||||
|
import { Home, Users, Disc3, Calendar } from 'lucide-react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 모바일 하단 네비게이션
|
||||||
|
*/
|
||||||
|
function MobileBottomNav() {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{ path: '/', label: '홈', icon: Home },
|
||||||
|
{ path: '/members', label: '멤버', icon: Users },
|
||||||
|
{ path: '/album', label: '앨범', icon: Disc3 },
|
||||||
|
{ path: '/schedule', label: '일정', icon: Calendar },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className="flex-shrink-0 bg-white border-t border-gray-200 z-50 safe-area-bottom">
|
||||||
|
<div className="flex items-center justify-around h-16">
|
||||||
|
{navItems.map((item) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
const isActive =
|
||||||
|
location.pathname === item.path ||
|
||||||
|
(item.path !== '/' && location.pathname.startsWith(item.path));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
key={item.path}
|
||||||
|
to={item.path}
|
||||||
|
onClick={() => window.scrollTo(0, 0)}
|
||||||
|
className={`flex flex-col items-center justify-center gap-1 w-full h-full transition-colors ${
|
||||||
|
isActive ? 'text-primary' : 'text-gray-400'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Icon size={22} strokeWidth={isActive ? 2.5 : 2} />
|
||||||
|
<span className="text-xs font-medium">{item.label}</span>
|
||||||
|
</NavLink>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MobileBottomNav;
|
||||||
26
frontend-temp/src/components/mobile/Header.jsx
Normal file
26
frontend-temp/src/components/mobile/Header.jsx
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { NavLink } from 'react-router-dom';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 모바일 헤더 컴포넌트
|
||||||
|
* @param {string} title - 페이지 제목 (없으면 fromis_9)
|
||||||
|
* @param {boolean} noShadow - 그림자 숨김 여부
|
||||||
|
*/
|
||||||
|
function MobileHeader({ title, noShadow = false }) {
|
||||||
|
return (
|
||||||
|
<header
|
||||||
|
className={`bg-white sticky top-0 z-50 ${noShadow ? '' : 'shadow-sm'}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-center h-14 px-4">
|
||||||
|
{title ? (
|
||||||
|
<span className="text-xl font-bold text-primary">{title}</span>
|
||||||
|
) : (
|
||||||
|
<NavLink to="/" className="text-xl font-bold text-primary">
|
||||||
|
fromis_9
|
||||||
|
</NavLink>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MobileHeader;
|
||||||
|
|
@ -1,70 +1,8 @@
|
||||||
import { NavLink, useLocation } from 'react-router-dom';
|
|
||||||
import { Home, Users, Disc3, Calendar } from 'lucide-react';
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import MobileHeader from './Header';
|
||||||
|
import MobileBottomNav from './BottomNav';
|
||||||
import '@/mobile.css';
|
import '@/mobile.css';
|
||||||
|
|
||||||
/**
|
|
||||||
* 모바일 헤더 컴포넌트
|
|
||||||
*/
|
|
||||||
function MobileHeader({ title, noShadow = false }) {
|
|
||||||
return (
|
|
||||||
<header
|
|
||||||
className={`bg-white sticky top-0 z-50 ${noShadow ? '' : 'shadow-sm'}`}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-center h-14 px-4">
|
|
||||||
{title ? (
|
|
||||||
<span className="text-xl font-bold text-primary">{title}</span>
|
|
||||||
) : (
|
|
||||||
<NavLink to="/" className="text-xl font-bold text-primary">
|
|
||||||
fromis_9
|
|
||||||
</NavLink>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 모바일 하단 네비게이션
|
|
||||||
*/
|
|
||||||
function MobileBottomNav() {
|
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
const navItems = [
|
|
||||||
{ path: '/', label: '홈', icon: Home },
|
|
||||||
{ path: '/members', label: '멤버', icon: Users },
|
|
||||||
{ path: '/album', label: '앨범', icon: Disc3 },
|
|
||||||
{ path: '/schedule', label: '일정', icon: Calendar },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<nav className="flex-shrink-0 bg-white border-t border-gray-200 z-50 safe-area-bottom">
|
|
||||||
<div className="flex items-center justify-around h-16">
|
|
||||||
{navItems.map((item) => {
|
|
||||||
const Icon = item.icon;
|
|
||||||
const isActive =
|
|
||||||
location.pathname === item.path ||
|
|
||||||
(item.path !== '/' && location.pathname.startsWith(item.path));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NavLink
|
|
||||||
key={item.path}
|
|
||||||
to={item.path}
|
|
||||||
onClick={() => window.scrollTo(0, 0)}
|
|
||||||
className={`flex flex-col items-center justify-center gap-1 w-full h-full transition-colors ${
|
|
||||||
isActive ? 'text-primary' : 'text-gray-400'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Icon size={22} strokeWidth={isActive ? 2.5 : 2} />
|
|
||||||
<span className="text-xs font-medium">{item.label}</span>
|
|
||||||
</NavLink>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 모바일 레이아웃 컴포넌트
|
* 모바일 레이아웃 컴포넌트
|
||||||
* @param {React.ReactNode} children - 페이지 컨텐츠
|
* @param {React.ReactNode} children - 페이지 컨텐츠
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
// 레이아웃
|
// 레이아웃
|
||||||
export { default as Layout } from './Layout';
|
export { default as Layout } from './Layout';
|
||||||
|
export { default as Header } from './Header';
|
||||||
|
export { default as BottomNav } from './BottomNav';
|
||||||
|
|
||||||
// 일정 컴포넌트
|
// 일정 컴포넌트
|
||||||
export { default as Calendar } from './Calendar';
|
export { default as Calendar } from './Calendar';
|
||||||
|
|
|
||||||
|
|
@ -57,3 +57,12 @@ export const MONTH_NAMES = [
|
||||||
'1월', '2월', '3월', '4월', '5월', '6월',
|
'1월', '2월', '3월', '4월', '5월', '6월',
|
||||||
'7월', '8월', '9월', '10월', '11월', '12월',
|
'7월', '8월', '9월', '10월', '11월', '12월',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/** 그룹 정보 */
|
||||||
|
export const GROUP_INFO = {
|
||||||
|
NAME: 'fromis_9',
|
||||||
|
NAME_KR: '프로미스나인',
|
||||||
|
DEBUT_DATE: '2018-01-24',
|
||||||
|
DEBUT_DATE_DISPLAY: '2018.01.24',
|
||||||
|
FANDOM_NAME: 'flover',
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
|
import { useMemo } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Calendar, ArrowRight, Music } from 'lucide-react';
|
import { Calendar, ArrowRight, Music } from 'lucide-react';
|
||||||
import { useMembers, useAlbums, useUpcomingSchedules } from '@/hooks';
|
import { useMembers, useAlbums, useUpcomingSchedules } from '@/hooks';
|
||||||
import { ScheduleCard } from '@/components/pc';
|
import { ScheduleCard } from '@/components/pc';
|
||||||
|
import { GROUP_INFO } from '@/constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PC 홈 페이지
|
* PC 홈 페이지
|
||||||
|
|
@ -18,10 +20,18 @@ function Home() {
|
||||||
// 다가오는 일정 (3개)
|
// 다가오는 일정 (3개)
|
||||||
const { data: upcomingSchedules = [] } = useUpcomingSchedules(3);
|
const { data: upcomingSchedules = [] } = useUpcomingSchedules(3);
|
||||||
|
|
||||||
|
// 활동 멤버 수
|
||||||
|
const activeMemberCount = useMemo(
|
||||||
|
() => members.filter((m) => !m.is_former).length,
|
||||||
|
[members]
|
||||||
|
);
|
||||||
|
|
||||||
// D+Day 계산
|
// D+Day 계산
|
||||||
const debutDate = new Date('2018-01-24');
|
const dDay = useMemo(() => {
|
||||||
const today = new Date();
|
const debutDate = new Date(GROUP_INFO.DEBUT_DATE);
|
||||||
const dDay = Math.floor((today - debutDate) / (1000 * 60 * 60 * 24)) + 1;
|
const today = new Date();
|
||||||
|
return Math.floor((today - debutDate) / (1000 * 60 * 60 * 24)) + 1;
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -67,10 +77,10 @@ function Home() {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{[
|
{[
|
||||||
{ value: '2018.01.24', label: '데뷔일' },
|
{ value: GROUP_INFO.DEBUT_DATE_DISPLAY, label: '데뷔일' },
|
||||||
{ value: `D+${dDay.toLocaleString()}`, label: 'D+Day' },
|
{ value: `D+${dDay.toLocaleString()}`, label: 'D+Day' },
|
||||||
{ value: '5', label: '멤버 수' },
|
{ value: String(activeMemberCount || '-'), label: '멤버 수' },
|
||||||
{ value: 'flover', label: '팬덤명' },
|
{ value: GROUP_INFO.FANDOM_NAME, label: '팬덤명' },
|
||||||
].map((stat, index) => (
|
].map((stat, index) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={index}
|
key={index}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue