Commit graph

99 commits

Author SHA1 Message Date
d4697ad996 refactor: 일정 API source 객체 구조 변경
- source_name, source_url → source: { name, url } 형태로 변경
- YouTube: schedule_youtube에서 video_id로 URL 생성
- X: schedule_x에서 post_id로 URL 생성
- 프론트엔드 전체 파일 source 객체 형태로 수정
- 문서 업데이트 (api.md, architecture.md, migration.md 등)
- tracks → album_tracks 테이블명 변경 반영

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 21:50:04 +09:00
5521c44fa9 feat: 어드민 사전 관리 기능 추가
- 형태소 분석기 사용자 사전 관리 페이지 추가
- 단어 추가/삭제/수정 시 즉시 저장 및 형태소 분석기 리로드
- 품사별 통계 및 필터링 기능
- 검색 기능 추가

백엔드:
- GET/PUT /api/schedules/suggestions/dict API 추가
- morpheme.js에 reloadMorpheme(), getUserDictPath() 함수 추가

프론트엔드:
- AdminScheduleDict.jsx 페이지 추가
- AdminSchedule.jsx에 사전 관리 버튼 추가
- 라우트 추가 (/admin/schedule/dict)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-18 13:53:51 +09:00
b89255780e refactor: 일정 API 응답 형식에 맞게 프론트엔드 수정
- API 응답(날짜별 그룹화)을 플랫 배열로 변환하는 로직 추가
- 별도 카테고리 API 호출 제거, 일정 데이터에서 카테고리 추출
- PC/모바일 Schedule.jsx, AdminSchedule.jsx 수정

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 17:52:15 +09:00
b0f7169226 refactor: API 라우트 구조 통합 및 파일 분리
- /api/admin/* + /api/* 분리 구조를 /api/*로 통합
- GET 요청은 공개, POST/PUT/DELETE는 인증 필요로 변경
- albums 라우트를 기능별 파일로 분리 (index, photos, teasers)
- 프론트엔드 API 호출 경로 업데이트

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 13:01:35 +09:00
428a74a703 feat: 대시보드 통계 API 추가
- /api/admin/stats 라우트 추가 (멤버, 앨범, 사진, 일정, 트랙 수)
- AdminDashboard에서 단일 API 호출로 통계 조회
- useQuery로 변경하여 중복 요청 방지
- AnimatedNumber 컴포넌트에 3자리 쉼표 및 애니메이션 적용

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 00:08:16 +09:00
86220bdd3d refactor: 앨범 관리 페이지 useEffect를 useQuery로 변경
- AdminAlbums.jsx: useQuery로 앨범 목록 조회, 삭제 후 캐시 무효화
- AdminAlbumForm.jsx: useQuery로 앨범 상세 조회, 저장 후 캐시 무효화
- AdminAlbumPhotos.jsx: useQuery로 앨범/사진/티저/멤버 조회
- backend: 정적 파일 서빙 추가 (프로덕션 모드용)
- backend: 앨범 라우트에 인증 미들웨어 적용

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 23:40:47 +09:00
f5ae81d21a feat: 앨범 관리 API 구현 및 프론트엔드 연동
Backend:
- 앨범 CRUD API 구현 (목록, 상세, 생성, 수정, 삭제)
- 앨범 사진 관리 API 구현 (업로드, 삭제, 티저 관리)
- 이미지 서비스에 앨범 관련 함수 추가
- Public 라우트 추가 (앨범, 멤버 공개 API)

Frontend:
- AdminAlbums.jsx admin API로 변경

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 23:16:41 +09:00
6462949bc7 refactor: 어드민 페이지 인증 로직을 useAdminAuth 훅으로 통합
- 8개 어드민 페이지에서 중복된 인증 코드 제거
- localStorage 직접 접근 → useAdminAuth 훅 사용
- authApi.hasToken()/verifyToken() 호출 제거
- 일관된 인증 상태 관리 (5분 캐시, 자동 리다이렉트)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 23:10:30 +09:00
d44537d870 멤버 관리 API 구현 및 프론트엔드 연동
Backend:
- 멤버 CRUD API 추가 (routes/admin/members.js)
- 이미지 업로드 서비스 추가 (services/image.js)
  - S3에 3가지 해상도로 저장 (original, medium_800, thumb_400)
- multipart 플러그인 등록

Frontend:
- useAdminAuth 커스텀 훅 추가 (토큰 검증 API 사용)
- AdminMembers, AdminMemberEdit useQuery로 변경
- 로그인 확인 로직 중복 제거
- 수정 완료 시 목록 페이지로 이동 및 토스트 표시

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-16 23:01:23 +09:00
1cb430907f refactor: 멤버 관리 페이지 UI 수정
- API 연동 임시 제거, 더미 데이터로 대체
- position 필드 제거
- 별명(nicknames) 태그 입력 UI 추가
- DB 변경: members 테이블 position/image_url 제거, image_id 추가
- DB 변경: member_nicknames 테이블 생성

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 21:53:55 +09:00
6275f0f92a feat: 생일 기능 추가 및 일정 페이지 개선
- 생일 카드 컴포넌트 추가 (PC/모바일)
- 생일 폭죽(confetti) 애니메이션 적용 (하루에 한 번)
- 생일 상세 페이지 추가 (/birthday/멤버이름/년도)
- 관리자 일정 페이지에 생일 표시 (수정/삭제 버튼 숨김)
- 일정 상세 페이지 404 에러 UI 개선
- 일정 상세 페이지 불필요한 재시도 방지 (retry: false)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 20:09:26 +09:00
255839a598 비디오 티저 썸네일 추출 기능 추가
- DB: album_teasers 테이블에 video_url 컬럼 추가
- 백엔드: 비디오 업로드 시 ffmpeg로 썸네일 추출 후 WebP 저장
- 백엔드: video_url에 MP4 URL 저장, 썸네일은 기존 URL 필드 사용
- 프론트엔드: 썸네일 이미지 표시, 클릭 시 video_url로 재생
- Flutter 앱: Teaser 모델에 videoUrl 필드 추가 및 비디오 재생 수정
- Docker: ffmpeg 설치 추가 (Dockerfile, docker-compose.dev.yml)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 11:59:58 +09:00
8e3cab9b10 feat(Category): 기본 카테고리 보호 및 ID 재정렬
- is_default 컬럼 추가 (기존 6개 카테고리 = 기본)
- 카테고리 ID 재정렬: 기타(1), 유튜브(2), X(3), 앨범(4), 팬사인회(5), 콘서트(6)
- 일정 테이블 category_id 동기화
- 기본 카테고리 삭제 불가 (백엔드 + 프론트엔드)
- 일정 사용 중인 카테고리 삭제 불가
2026-01-11 21:56:35 +09:00
9c2ff7458d feat(Search): Admin Schedule 추천 검색어 연동 + 빈 상태 드롭다운 숨김 2026-01-11 21:39:23 +09:00
727b05f0f5 feat(Search): Redis 기반 bi-gram 추천 검색어 시스템 구현
- MariaDB 테이블 추가 (search_queries, word_pairs)
- Redis 컨테이너 추가 (Sorted Set 캐싱)
- 백엔드 suggestions 서비스 및 API 구현
- 검색 실행 시 검색어 저장 (bi-gram 학습)
- PC Schedule 프론트엔드 연동 완료
2026-01-11 21:33:55 +09:00
3d2a6555f8 fix(Schedule): 무한 스크롤 중복 요청 방지
- inView가 false→true로 변경될 때만 fetchNextPage 실행
- prevInViewRef를 사용하여 이전 상태 추적
- PC 일정, Admin 일정 페이지 모두 적용
2026-01-11 19:06:52 +09:00
09706e42e3 feat(AdminSchedule): 검색어 추천 드롭다운 추가
- 검색 버튼을 초록색 돋보기 아이콘으로 변경
- 애니메이션 scale+opacity로 개선
- 검색 모드에서 빈 일정 메시지 숨김
- 추천 검색어 드롭다운 (유튜브 스타일)
- 방향키 선택, 외부 클릭 닫기 지원
- 검색 모드 진입 시 기존 카테고리 유지
2026-01-11 16:08:22 +09:00
90f5a9a90a feat(Schedule): 검색어 추천 UI 프로토타입 구현
- PC/Admin 스케줄 페이지에 검색어 추천 드롭다운 추가
- 3영역 검색창 레이아웃 (뒤로가기 / 입력 / 검색 버튼)
- 방향키로 추천 검색어 선택 시 입력창 반영 (유튜브 스타일)
- 외부 클릭 시 드롭다운 닫기
- 검색 모드 진입 시 기존 카테고리 유지
- 검색 모드 종료 시 스크롤 위치 초기화
- 전환 애니메이션 개선 (scale + opacity)
2026-01-11 15:58:20 +09:00
2e46d1cf71 AdminSchedule에도 AdminLayout 적용
- AdminHeader를 AdminLayout으로 교체
- 내부 레이아웃을 h-full flex flex-col overflow-hidden으로 조정
- 모든 Admin 페이지가 통일된 레이아웃 구조 사용
2026-01-11 12:13:59 +09:00
1d86c6b841 Admin 페이지에 AdminLayout 적용하여 헤더 고정 + 본문 스크롤 구조 구현
- AdminLayout.jsx 컴포넌트 생성 (헤더 고정 + overflow-y-auto)
- AdminDashboard, AdminMembers, AdminMemberEdit에 적용
- AdminAlbums, AdminAlbumForm, AdminAlbumPhotos에 적용
- AdminScheduleCategory, AdminScheduleBots, AdminScheduleForm에 적용
- AdminSchedule은 내부 스크롤 처리로 자동 감지하여 제외
2026-01-11 12:12:46 +09:00
622839b0e8 feat: Meilisearch 동기화 봇 추가 및 시간대 관련 수정
봇 시스템:
- Meilisearch 동기화 봇 추가 (meilisearch-bot.js)
- bots 테이블 type enum에 meilisearch 추가
- youtube-scheduler.js에 meilisearch 봇 분기 추가
- admin.js API에서 meilisearch 봇 지원

봇 관리 페이지 개선 (AdminScheduleBots.jsx):
- Meilisearch 공식 로고 아이콘 추가
- Meilisearch 봇 통계: 동기화 수/소요 시간 표시
- 봇 타입별 배경색 (X: 검정, Meilisearch: #ddf1fd, YouTube: 빨강)

시간대 정리:
- MariaDB KST 설정으로 DATE_ADD(NOW(), INTERVAL 9 HOUR) → NOW() 변경
- youtube-bot.js, x-bot.js에서 10곳 수정
2026-01-10 18:59:39 +09:00
59e5a1d47b feat: X 봇 구현 및 봇 관리 기능 개선
- X 봇 서비스 추가 (x-bot.js)
  - Nitter를 통한 @realfromis_9 트윗 수집
  - 트윗을 일정으로 자동 저장 (카테고리 12)
  - 관리 채널 외 유튜브 링크 감지 시 별도 일정 추가
  - 1분 간격 동기화 지원

- DB 스키마 변경
  - bots.type enum 수정 (vlive, weverse 제거, x 추가)
  - bot_x_config 테이블 추가

- 봇 스케줄러 수정 (youtube-scheduler.js)
  - 봇 타입별 동기화 함수 분기 (syncBot)
  - X 봇 지원 추가

- 관리자 페이지 개선 (AdminScheduleBots.jsx)
  - 봇 타입별 아이콘 표시 (YouTube/X)
  - X 아이콘 SVG 컴포넌트 추가

- last_added_count 로직 수정
  - 추가 항목 없으면 이전 값 유지 (0으로 초기화 방지)

- 기존 X 일정에서 유튜브 영상 추출 스크립트 추가
2026-01-10 17:06:23 +09:00
0efa0a8e5c feat: AdminSchedule.jsx에 동적 높이 가상 스크롤 적용
- measureElement와 data-index 추가
- 고정 높이 제거하여 내용에 맞게 자동 조절
2026-01-10 09:52:34 +09:00
4ecc16347c fix: 일정 페이지 최소 높이 및 관리 페이지 글씨 크기 수정
- Schedule.jsx: 검색 결과 아이템에 min-h-[100px] 추가
- AdminSchedule.jsx: 글씨 크기 및 아이콘 크기 원복
2026-01-10 09:50:56 +09:00
043f8b465d fix: 가상 스크롤 버그 수정
- searchSchedules 미정의 오류 수정 (setSearchTerm만 사용)
- Schedule.jsx 항목 간 여백 추가 (ITEM_HEIGHT 136px)
2026-01-10 09:40:40 +09:00
ad2d501c39 feat: 일정 검색에 가상 스크롤 최적화 적용
- @tanstack/react-virtual 라이브러리 추가
- Schedule.jsx, AdminSchedule.jsx에 useVirtualizer 적용
- 검색 결과가 많아도 DOM에는 화면에 보이는 요소만 렌더링
- 스크롤 성능 대폭 향상
2026-01-10 09:34:18 +09:00
7192379eb0 chore: 일정 검색 페이징 limit을 20개로 변경
- Schedule.jsx, AdminSchedule.jsx SEARCH_LIMIT 5 -> 20
2026-01-10 09:20:27 +09:00
9986b918d8 fix: 검색 모드에서 카테고리 필터링 적용
- 검색 결과에도 selectedCategories로 필터링 추가
- Schedule.jsx, AdminSchedule.jsx 수정
2026-01-10 09:16:15 +09:00
bedda590eb fix: 검색 모드에서 카테고리 카운트 수정
- 검색 모드일 때 selectedDate 조건 제거하여 모든 검색 결과의 카테고리 표시
- Schedule.jsx, AdminSchedule.jsx 수정
2026-01-10 09:13:49 +09:00
97767bc5ca fix: 일정 목록 높이 자동 계산으로 잘림 문제 해결
- main을 flex flex-col로 변경
- 브레드크럼, 타이틀에 flex-shrink-0 추가
- 그리드에 flex-1 min-h-0 추가
- 일정 목록 컬럼에 flex flex-col min-h-0 추가
- 스크롤 컨테이너에서 max-h 고정값 제거하고 flex-1로 변경
2026-01-10 00:31:34 +09:00
04ce4beba4 fix: 일정 관리 페이지 UI 수정
1. 외부 스크롤 제거 (h-screen overflow-hidden flex flex-col)
2. HTML 엔티티 디코딩 함수 추가 (&lt; &gt; 등 올바르게 표시)
3. decodeHtmlEntities 함수를 컴포넌트 외부로 이동하여 ScheduleItem에서 접근 가능
2026-01-10 00:27:32 +09:00
0f7d3d6f39 Revert "fix: 관리 페이지 날짜 상태 조건부 초기화"
This reverts commit 2dd5219265.
2026-01-10 00:20:04 +09:00
2dd5219265 fix: 관리 페이지 날짜 상태 조건부 초기화
문제: 일정 관리 페이지에서 날짜 선택 후 홈으로 갔다왔을 때
오늘 날짜로 초기화가 안됨

해결:
- useScheduleStore에 preserveState 플래그 추가
- AdminSchedule에서 preserveState가 false면 오늘 날짜로 초기화
- AdminScheduleForm, AdminScheduleBots에서 일정 관리로 돌아갈 때
  preserveState를 true로 설정하여 상태 유지
2026-01-10 00:19:06 +09:00
8e01692d6e refactor: ConfirmDialog 추가 적용 및 일정 제목 truncate 제거
수정된 파일:
- AdminScheduleCategory.jsx (50줄 → 15줄, 35줄 제거)
- AdminAlbumPhotos.jsx (60줄 → 15줄, 45줄 제거)
- AdminScheduleForm.jsx (55줄 → 12줄, 43줄 제거)
- AdminSchedule.jsx 일정 제목 truncate 2곳 제거

총 약 220줄의 중복 코드 제거
2026-01-09 23:44:37 +09:00
d52b43397c refactor: ConfirmDialog 공통 컴포넌트 생성 및 적용
새로 생성된 파일:
- components/admin/ConfirmDialog.jsx (109줄)
  - 삭제 확인 등 위험한 작업을 위한 공통 다이얼로그
  - Props: isOpen, onClose, onConfirm, title, message, loading, variant 등

수정된 파일:
- pages/pc/admin/AdminAlbums.jsx (60줄 → 15줄)
- pages/pc/admin/AdminSchedule.jsx (70줄 → 17줄)

총 약 100줄의 중복 코드 제거
2026-01-09 23:36:34 +09:00
5c812b7ac5 fix: AdminAlbumPhotos 로딩 시 헤더 유지
- 로딩 상태에서도 AdminHeader 표시
- 콘텐츠 영역만 스피너로 대체
- 페이지 전환 시 헤더 깜빡임 해결
2026-01-09 23:28:58 +09:00
b37ca360a5 fix: AdminAlbumForm 페이지 전환 애니메이션 제거
- motion.div를 일반 div로 변경
- pageVariants 변수 제거
- 다른 Admin 페이지와 일관성 유지
- 페이지 전환 시 헤더 깜빡임 해결
2026-01-09 23:25:48 +09:00
97fb4b7964 refactor: AdminHeader 공통 컴포넌트를 모든 Admin 페이지에 적용
새로 생성된 파일:
- components/admin/AdminHeader.jsx (47줄)

수정된 파일 (10개):
- pages/pc/admin/AdminMembers.jsx
- pages/pc/admin/AdminMemberEdit.jsx
- pages/pc/admin/AdminAlbums.jsx
- pages/pc/admin/AdminAlbumForm.jsx
- pages/pc/admin/AdminAlbumPhotos.jsx
- pages/pc/admin/AdminSchedule.jsx
- pages/pc/admin/AdminScheduleForm.jsx
- pages/pc/admin/AdminScheduleBots.jsx
- pages/pc/admin/AdminScheduleCategory.jsx
- pages/pc/admin/AdminDashboard.jsx

각 파일에서:
- 중복 헤더 JSX 제거 (24줄 → 1줄)
- handleLogout 함수 제거 (4~6줄)
- LogOut import 제거

총 약 300줄의 중복 코드 제거
2026-01-09 23:18:48 +09:00
7f9c53b53a refactor: AdminHeader 공통 컴포넌트 생성 및 AdminMembers 적용
새로 생성된 파일:
- components/admin/AdminHeader.jsx (47줄)
  - 로고, Admin 배지, 사용자 정보, 로그아웃 버튼 포함

수정된 파일:
- pages/pc/admin/AdminMembers.jsx
  - AdminHeader import 추가
  - 중복 헤더 JSX 제거 (24줄 -> 1줄)
  - handleLogout 함수 제거 (5줄)

나머지 9개 Admin 페이지도 동일한 패턴으로 적용 가능
2026-01-09 23:09:11 +09:00
07a0c30f0f refactor: NumberPicker, CustomTimePicker 공통 컴포넌트로 분리
새로 생성된 파일:
- components/admin/NumberPicker.jsx (185줄)
- components/admin/CustomTimePicker.jsx (176줄)

수정된 파일:
- pages/pc/admin/AdminScheduleForm.jsx (362줄 제거)

AdminScheduleForm에서 중복 정의된 NumberPicker, CustomTimePicker를
별도 파일로 분리하고 import로 대체.

총 코드 변화: 361줄 → 0줄 (361줄 제거)
2026-01-09 23:05:38 +09:00
0b00055773 refactor: useToast 커스텀 훅으로 Toast 로직 통합
- hooks/useToast.js 생성 (44줄)
- 적용 파일 (9개):
  - AdminMembers.jsx
  - AdminMemberEdit.jsx
  - AdminAlbums.jsx
  - AdminAlbumForm.jsx
  - AdminAlbumPhotos.jsx
  - AdminSchedule.jsx
  - AdminScheduleForm.jsx
  - AdminScheduleBots.jsx
  - AdminScheduleCategory.jsx
- 각 파일에서 중복된 toast useState/useEffect 제거
- showSuccess/showError 편의 메서드 활용

총 약 70줄의 중복 코드 제거
2026-01-09 22:57:34 +09:00
8424c3422d fix: Schedule 페이지 오류 수정 및 년월 선택 스타일 통일
- Schedule.jsx: yearRange, monthNames 변수 누락 수정
- Schedule.jsx, AdminSchedule.jsx: 현재 년도/월 테두리 제거, 글씨색만 유지
- CustomDatePicker.jsx: 동일 스타일 적용
2026-01-09 22:47:32 +09:00
7867eb8928 refactor: CustomDatePicker 공통 컴포넌트로 분리
- components/admin/CustomDatePicker.jsx 생성 (269줄)
- AdminMemberEdit에서 중복 제거 (-237줄)
- AdminAlbumForm에서 중복 제거 (-273줄)
- AdminScheduleForm에서 중복 제거 (-349줄)
- 현재 년도/월 표시: 테두리 제거, 글씨색만 유지
- 오늘 날짜: 배경색 제거, 글씨색만 유지
- 요일/일요일/토요일 색상 구분 추가

총 코드 감소: 약 860줄
2026-01-09 22:42:33 +09:00
8124a1abe1 refactor: 모든 Admin 페이지 API 모듈화 완료
- AdminMemberEdit API 모듈 적용
- AdminScheduleForm API 모듈 적용
- AdminAlbumPhotos API 모듈 적용 (업로드는 SSE 유지)
- api/admin/albums.js에 getAlbumTeasers, deleteAlbumTeaser 추가

총 적용 완료:
- Public 페이지: 7개
- Admin 페이지: 10개 전체 완료
2026-01-09 22:26:37 +09:00
f006309ef4 refactor: 남은 Admin 페이지 API 모듈 적용 완료
- AdminAlbums API 모듈 적용

총 적용 완료:
- Public 페이지: 7개 (PC 6 + 모바일 1)
- Admin 페이지: 7개 (Schedule, ScheduleBots, ScheduleCategory, Login, Dashboard, Albums, Schedule)
2026-01-09 22:16:25 +09:00
fbb68e66ee refactor: 인증 API 모듈화 및 Admin 페이지 적용 완료
- api/admin/auth.js 인증 모듈 생성 (verifyToken, login, logout, hasToken 등)
- categories.js reorderCategories 경로 수정
- AdminScheduleCategory 전체 API 모듈 적용
- AdminLogin API 모듈 적용
- AdminDashboard API 모듈 적용

남은 작업: AdminAlbums, AdminAlbumPhotos, AdminMemberEdit, AdminScheduleForm
2026-01-09 22:15:10 +09:00
0722ca10f3 refactor: API 중앙화 최종 (5/5)
- Schedule.jsx import 경로 오류 수정
- AdminSchedule.jsx API 모듈 적용 (schedulesApi, categoriesApi)
- 전체: PC public 6개, 모바일 Home 1개, Admin 2개 적용 완료
2026-01-09 22:09:42 +09:00
9886048a4c refactor: API 중앙화 시작 (1/3)
- api/ 디렉토리 구조 생성
  - index.js: 공통 fetch 래퍼
  - schedules.js, albums.js, members.js: 공개 API
  - admin/: 어드민 API (bots, albums, categories, members, schedules)
- Schedule.jsx: API 모듈 적용
- AdminScheduleBots.jsx: API 모듈 적용
2026-01-09 21:56:32 +09:00
8db0a574ab fix: 봇 관리 페이지 애니메이션 최적화
- 시작/중지 시 로컬 상태만 업데이트하여 전체 목록 새로고침 방지
- isInitialLoad 상태로 첫 로드 시에만 애니메이션 실행
- 새로고침 버튼 클릭 시에도 애니메이션 실행
2026-01-09 20:31:47 +09:00
fc35a35987 perf: AdminSchedule 성능 최적화
- filteredSchedules useMemo로 불필요한 재계산 방지
- ScheduleItem React.memo 컴포넌트로 분리하여 리렌더링 방지
- categoryCounts useMemo 맵으로 O(1) 카테고리 카운트 조회
- 카테고리 카운트를 선택된 날짜 기준으로 계산
- useDeferredValue로 달력 점 표시 지연 처리하여 UI 응답성 향상
- selectedDate 변경 시 스크롤 맨 위로 초기화
2026-01-09 19:26:52 +09:00