fromis_9/docs/frontend-improvement.md
caadiq 218b825878 refactor: 일정 관리 컴포넌트 분리 (Phase 3)
- AnimatedNumber 공통 컴포넌트 추출 (32줄)
- BotCard 컴포넌트 분리 + XIcon, MeilisearchIcon 포함 (233줄)
- CategoryFormModal 컴포넌트 분리 (195줄)
- ScheduleBots.jsx: 570줄 → 339줄 (231줄 감소)
- ScheduleCategory.jsx: 441줄 → 289줄 (152줄 감소)
- 문서 업데이트: 개선 결과 테이블 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 10:21:06 +09:00

7.3 KiB

일정 관리 페이지 개선 계획

대상 파일

파일 라인 수 역할
Schedules.jsx 1159 일정 목록/검색
ScheduleForm.jsx 765 일정 추가/수정 폼
ScheduleDict.jsx 572 사전 관리
ScheduleBots.jsx 570 봇 관리
ScheduleCategory.jsx 466 카테고리 관리

1. 공통 코드 중복 문제

1.1 colorMap / getColorStyle 중복

현황: 3개 파일에서 동일한 코드 반복

// Schedules.jsx:206-224
// ScheduleForm.jsx:97-117
// ScheduleCategory.jsx:24-36
const colorMap = {
  blue: 'bg-blue-500',
  green: 'bg-green-500',
  // ...
};

const getColorStyle = (color) => {
  if (!color) return { className: 'bg-gray-500' };
  if (color.startsWith('#')) {
    return { style: { backgroundColor: color } };
  }
  return { className: colorMap[color] || 'bg-gray-500' };
};

개선안:

utils/color.js 생성
├── COLOR_MAP (상수)
├── COLOR_OPTIONS (ScheduleCategory에서 사용하는 색상 옵션)
└── getColorStyle(color) (함수)

1.2 colorOptions 상수

현황: ScheduleCategory.jsx에만 있지만 확장성 고려

// ScheduleCategory.jsx:13-22
const colorOptions = [
  { id: 'blue', name: '파란색', bg: 'bg-blue-500', hex: '#3b82f6' },
  // ...
];

개선안: constants/colors.js 또는 utils/color.js에 통합


2. 파일별 개선 사항

2.1 Schedules.jsx (1159줄)

검색 관련 상태/로직 복잡

현황: 검색 관련 상태가 10개 이상, useEffect 5개

// 검색 관련 상태 (55-65줄)
const [showSuggestions, setShowSuggestions] = useState(false);
const [selectedSuggestionIndex, setSelectedSuggestionIndex] = useState(-1);
const [originalSearchQuery, setOriginalSearchQuery] = useState('');
const [suggestions, setSuggestions] = useState([]);
const [isLoadingSuggestions, setIsLoadingSuggestions] = useState(false);

개선안: useScheduleSearch 커스텀 훅 분리

// hooks/pc/admin/useScheduleSearch.js
export function useScheduleSearch() {
  // 검색 상태 및 로직 캡슐화
  return {
    searchInput, setSearchInput,
    suggestions, isLoadingSuggestions,
    handleSearch, handleSuggestionSelect,
    // ...
  };
}

달력 로직 분리 가능

현황: 달력 관련 계산이 컴포넌트 내부에 산재

// 161-181줄
const year = currentDate.getFullYear();
const month = currentDate.getMonth();
const firstDay = new Date(year, month, 1).getDay();
// ...

개선안: 기존 utils/date.js에 달력 헬퍼 함수 추가 또는 useCalendar 훅 생성


2.2 ScheduleForm.jsx (765줄)

fetchSchedule 함수 중복 설정

현황: formData를 두 번 설정 (비효율)

// 140-158줄: 첫 번째 setFormData
setFormData({
  title: data.title || '',
  startDate: data.date ? formatDate(data.date) : '',
  // ...
});

// 163-184줄: 두 번째 setFormData (기존 이미지 처리 시)
if (data.images && data.images.length > 0) {
  setFormData((prev) => ({
    ...prev,
    title: data.title || '',  // 중복!
    // ...
  }));
}

개선안: 하나의 setFormData로 통합

const initialFormData = {
  title: data.title || '',
  startDate: data.date ? formatDate(data.date) : '',
  // ...
  images: data.images?.map((img) => ({ id: img.id, url: img.image_url })) || [],
};
setFormData(initialFormData);

2.3 ScheduleDict.jsx (572줄)

generateId 일관성

현황: generateId를 useCallback으로 정의했지만, parseDict 내부에서는 인라인으로 같은 로직 사용

// 113-116줄
const generateId = useCallback(
  () => `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
  []
);

// 128줄 (parseDict 내부)
id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,

개선안: generateId를 외부 유틸 함수로 분리하거나, parseDict에서 generateId 참조


2.4 ScheduleBots.jsx (570줄)

인라인 컴포넌트 분리

현황: AnimatedNumber, XIcon, MeilisearchIcon이 파일 내부에 정의

// 42-65줄
function AnimatedNumber({ value, className = '' }) { ... }

// 68-72줄
const XIcon = ({ size = 20, fill = 'currentColor' }) => ( ... );

// 75-128줄
const MeilisearchIcon = ({ size = 20 }) => ( ... );

개선안:

components/common/
├── AnimatedNumber.jsx  (재사용 가능한 애니메이션 숫자)
└── icons/
    ├── XIcon.jsx
    └── MeilisearchIcon.jsx

봇 카드 컴포넌트 분리

현황: 봇 카드 렌더링이 414-559줄로 약 145줄

개선안:

// components/pc/admin/bot/BotCard.jsx
function BotCard({ bot, onToggle, onSync, syncing }) {
  // ...
}

2.5 ScheduleCategory.jsx (466줄)

모달 컴포넌트 인라인

현황: 카테고리 추가/수정 모달이 284-445줄로 약 160줄

개선안:

// components/pc/admin/schedule/CategoryFormModal.jsx
function CategoryFormModal({ isOpen, onClose, category, onSave }) {
  // 색상 선택, 폼 로직 포함
}

3. 개선 우선순위

Phase 1: 중복 코드 제거 (빠른 효과) 완료

  1. utils/color.js 생성 - COLOR_MAP, COLOR_OPTIONS, getColorStyle 통합
  2. 3개 파일에서 import로 교체
    • Schedules.jsx: 1159줄 → 1139줄 (20줄 감소)
    • ScheduleForm.jsx: 765줄 → 743줄 (22줄 감소)
    • ScheduleCategory.jsx: 466줄 → 441줄 (25줄 감소)

Phase 2: 커스텀 훅 분리 (복잡도 감소) 완료

  1. useScheduleSearch 훅 생성 - Schedules.jsx 검색 로직 분리
    • 검색어 자동완성, 무한 스크롤, 키보드 네비게이션 캡슐화
    • Schedules.jsx: 1139줄 → 1009줄 (130줄 감소)
  2. 달력 관련 로직 정리 (선택사항, 현재 규모 적절)

Phase 3: 컴포넌트 분리 (재사용성) 완료

  1. AnimatedNumber 공통 컴포넌트화 → components/common/AnimatedNumber.jsx (32줄)
  2. BotCard 컴포넌트 분리 → components/pc/admin/bot/BotCard.jsx (233줄)
  3. CategoryFormModal 컴포넌트 분리 → components/pc/admin/schedule/CategoryFormModal.jsx (195줄)
  4. SVG 아이콘 분리 (XIcon, MeilisearchIcon) → BotCard.jsx에 포함
    • ScheduleBots.jsx: 570줄 → 339줄 (231줄 감소)
    • ScheduleCategory.jsx: 441줄 → 289줄 (152줄 감소)

Phase 4: 코드 정리

  1. ScheduleForm.jsx - fetchSchedule 중복 제거
  2. ScheduleDict.jsx - generateId 일관성

4. 개선 결과

파일 개선 전 개선 후 감소
Schedules.jsx 1159줄 1009줄 150줄
ScheduleForm.jsx 765줄 743줄 22줄
ScheduleDict.jsx 572줄 572줄 -
ScheduleBots.jsx 570줄 339줄 231줄
ScheduleCategory.jsx 466줄 289줄 177줄
합계 3532줄 2952줄 580줄

새로 생성된 파일

파일 라인 수 역할
utils/color.js 35줄 색상 상수/유틸
hooks/pc/admin/useScheduleSearch.js 217줄 검색 로직 훅
components/common/AnimatedNumber.jsx 32줄 숫자 애니메이션
components/pc/admin/bot/BotCard.jsx 233줄 봇 카드
components/pc/admin/schedule/CategoryFormModal.jsx 195줄 카테고리 폼 모달