From 1ad5f6a907f988a791b7d572311adb0090c10c77 Mon Sep 17 00:00:00 2001 From: caadiq Date: Fri, 2 Jan 2026 23:35:36 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=A9=A4=EB=B2=84=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0=20-=20=EC=A0=84/=ED=98=84=EC=9E=AC=20?= =?UTF-8?q?=EB=A9=A4=EB=B2=84=20=EA=B5=AC=EB=B6=84,=20D+Day=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C,=20UI=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/routes/members.js | 2 +- download_photos.sh | 53 +++++++++ frontend/src/pages/pc/AlbumDetail.jsx | 2 +- frontend/src/pages/pc/Home.jsx | 2 +- frontend/src/pages/pc/Members.jsx | 73 ++++++++++--- .../src/pages/pc/admin/AdminAlbumForm.jsx | 14 ++- .../src/pages/pc/admin/AdminAlbumPhotos.jsx | 102 ++++++++++++++---- 7 files changed, 205 insertions(+), 43 deletions(-) create mode 100755 download_photos.sh diff --git a/backend/routes/members.js b/backend/routes/members.js index 5e91739..b5e7f3c 100644 --- a/backend/routes/members.js +++ b/backend/routes/members.js @@ -7,7 +7,7 @@ const router = express.Router(); router.get("/", async (req, res) => { try { const [rows] = await pool.query( - "SELECT id, name, name_en, birth_date, position, image_url, instagram FROM members ORDER BY id" + "SELECT id, name, birth_date, position, image_url, instagram, is_former FROM members ORDER BY is_former, birth_date" ); res.json(rows); } catch (error) { diff --git a/download_photos.sh b/download_photos.sh new file mode 100755 index 0000000..6bee747 --- /dev/null +++ b/download_photos.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +# fromis_9 Photos 테이블에서 이미지 다운로드 스크립트 +# 앨범별로 폴더 분류하여 저장 + +OUTPUT_DIR="/docker/fromis_9/downloaded_photos" +mkdir -p "$OUTPUT_DIR" + +# MariaDB에서 데이터 가져오기 +docker exec mariadb mariadb -u admin -p'auddnek0403!' fromis_9 -N -e "SELECT photo_id, album_name, photo FROM Photos;" | while IFS=$'\t' read -r photo_id album_name photo_url; do + # 앨범명에서 특수문자 제거하여 폴더명 생성 + folder_name=$(echo "$album_name" | sed 's/[^a-zA-Z0-9가-힣 ]/_/g' | sed 's/ */_/g') + + # 폴더 생성 + mkdir -p "$OUTPUT_DIR/$folder_name" + + # 파일명 생성 (photo_id 기반) + filename="${photo_id}.jpg" + filepath="$OUTPUT_DIR/$folder_name/$filename" + + # 이미 다운로드된 파일은 건너뛰기 + if [ -f "$filepath" ]; then + echo "Skip: $filepath (already exists)" + continue + fi + + # 다운로드 + echo "Downloading: $album_name/$filename" + curl -s -L -o "$filepath" "$photo_url" + + # 다운로드 실패 시 삭제 + if [ ! -s "$filepath" ]; then + rm -f "$filepath" + echo "Failed: $filepath" + fi + + # Rate limiting (0.2초 대기) + sleep 0.2 +done + +echo "Download complete!" +echo "Saved to: $OUTPUT_DIR" + +# 결과 요약 +echo "" +echo "=== Summary ===" +for dir in "$OUTPUT_DIR"/*/; do + if [ -d "$dir" ]; then + count=$(ls -1 "$dir" 2>/dev/null | wc -l) + dirname=$(basename "$dir") + echo "$dirname: $count files" + fi +done diff --git a/frontend/src/pages/pc/AlbumDetail.jsx b/frontend/src/pages/pc/AlbumDetail.jsx index d92965d..878f0bb 100644 --- a/frontend/src/pages/pc/AlbumDetail.jsx +++ b/frontend/src/pages/pc/AlbumDetail.jsx @@ -318,7 +318,7 @@ function AlbumDetail() {

{track.title}

{track.is_title_track === 1 && ( - 타이틀 + TITLE )} diff --git a/frontend/src/pages/pc/Home.jsx b/frontend/src/pages/pc/Home.jsx index 6faec22..5b1b0a6 100644 --- a/frontend/src/pages/pc/Home.jsx +++ b/frontend/src/pages/pc/Home.jsx @@ -92,7 +92,7 @@ function Home() {
- {members.map((member, index) => ( + {members.filter(m => !m.is_former).map((member, index) => (
- {/* 멤버 그리드 */} + {/* 현재 멤버 그리드 */}
- {members.map((member, index) => ( + {members.filter(m => !m.is_former).map((member, index) => ( {/* 인스타그램 링크 */} - - - Instagram - + {member.instagram && ( + + + Instagram + + )}
{/* 호버 효과 - 컬러 바 */} @@ -109,6 +111,43 @@ function Members() { ))} + {/* 탈퇴 멤버 섹션 - 콤팩트한 가로 리스트 */} + {members.filter(m => m.is_former).length > 0 && ( + +

전 멤버

+
+ {members.filter(m => m.is_former).map((member, index) => ( + + {/* 작은 원형 이미지 */} +
+ {member.name} +
+ {/* 이름과 포지션 */} +
+

{member.name}

+

{member.position || ''}

+
+
+ ))} +
+
+ )} + {/* 그룹 정보 */}
-

{stats.debutYear}

-

데뷔 연도

+

2018.01.24

+

데뷔일

+
+
+

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

+

D+Day

{stats.memberCount}

멤버 수

-
-

{stats.albumCount}

-

앨범 수

-

{stats.fandomName}

팬덤명

diff --git a/frontend/src/pages/pc/admin/AdminAlbumForm.jsx b/frontend/src/pages/pc/admin/AdminAlbumForm.jsx index 0261ec7..abaddcf 100644 --- a/frontend/src/pages/pc/admin/AdminAlbumForm.jsx +++ b/frontend/src/pages/pc/admin/AdminAlbumForm.jsx @@ -424,7 +424,19 @@ function AdminAlbumForm() { const handleInputChange = (e) => { const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + + // 앨범명 변경 시 RustFS 폴더명 자동 생성 + if (name === 'title') { + const folderName = value + .toLowerCase() + .replace(/[\s.]+/g, '-') // 띄어쓰기, 점을 하이픈으로 + .replace(/[^a-z0-9가-힣-]/g, '') // 특수문자 제거 (영문, 숫자, 한글, 하이픈만 유지) + .replace(/-+/g, '-') // 연속 하이픈 하나로 + .replace(/^-|-$/g, ''); // 앞뒤 하이픈 제거 + setFormData(prev => ({ ...prev, title: value, folder_name: folderName })); + } else { + setFormData(prev => ({ ...prev, [name]: value })); + } }; const handleCoverChange = (e) => { diff --git a/frontend/src/pages/pc/admin/AdminAlbumPhotos.jsx b/frontend/src/pages/pc/admin/AdminAlbumPhotos.jsx index 14e8971..1919c70 100644 --- a/frontend/src/pages/pc/admin/AdminAlbumPhotos.jsx +++ b/frontend/src/pages/pc/admin/AdminAlbumPhotos.jsx @@ -952,10 +952,16 @@ function AdminAlbumPhotos() { key={`order-${file.id}-${index}-${startNumber}`} onBlur={(e) => { const val = e.target.value.trim(); + // 스크롤 위치 저장 + const scrollY = window.scrollY; if (val && !isNaN(val)) { moveToPosition(file.id, val); } e.target.value = String(pendingFiles.findIndex(f => f.id === file.id) + 1).padStart(2, '0'); + // 스크롤 위치 복원 + requestAnimationFrame(() => { + window.scrollTo(0, scrollY); + }); }} onKeyDown={(e) => { if (e.key === 'Enter') { @@ -1010,27 +1016,51 @@ function AdminAlbumPhotos() {
{/* 멤버 태깅 (단체는 비활성화) */} -
- 멤버: - {file.groupType === 'group' ? ( - 단체 사진은 멤버 태깅이 필요 없습니다 - ) : ( - members.map(member => ( - - )) - )} - {file.groupType === 'solo' && ( - (한 명만 선택) +
+
+ 멤버: + {file.groupType === 'group' ? ( + 단체 사진은 멤버 태깅이 필요 없습니다 + ) : ( + <> + {/* 현재 멤버 */} + {members.filter(m => !m.is_former).map(member => ( + + ))} + + )} + {file.groupType === 'solo' && ( + (한 명만 선택) + )} +
+ {/* 전 멤버 (다음 줄) */} + {file.groupType !== 'group' && members.filter(m => m.is_former).length > 0 && ( +
+ + {members.filter(m => m.is_former).map(member => ( + + ))} +
)}
@@ -1124,7 +1154,8 @@ function AdminAlbumPhotos() { 멤버 {bulkEdit.groupType === 'solo' ? '(1명)' : '(다중 선택)'}
- {members.map(member => ( + {/* 현재 멤버 */} + {members.filter(m => !m.is_former).map(member => ( ))} + {/* 구분선 */} + {members.filter(m => m.is_former).length > 0 && ( + | + )} + {/* 탈퇴 멤버 */} + {members.filter(m => m.is_former).map(member => ( + + ))}
)}