refactor: 모든 Admin 페이지 API 모듈화 완료
- AdminMemberEdit API 모듈 적용 - AdminScheduleForm API 모듈 적용 - AdminAlbumPhotos API 모듈 적용 (업로드는 SSE 유지) - api/admin/albums.js에 getAlbumTeasers, deleteAlbumTeaser 추가 총 적용 완료: - Public 페이지: 7개 - Admin 페이지: 10개 전체 완료
This commit is contained in:
parent
f006309ef4
commit
8124a1abe1
4 changed files with 49 additions and 97 deletions
|
|
@ -48,3 +48,15 @@ export async function deleteAlbumPhoto(albumId, photoId) {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 앨범 티저 목록 조회
|
||||||
|
export async function getAlbumTeasers(albumId) {
|
||||||
|
return fetchAdminApi(`/api/admin/albums/${albumId}/teasers`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 앨범 티저 삭제
|
||||||
|
export async function deleteAlbumTeaser(albumId, teaserId) {
|
||||||
|
return fetchAdminApi(`/api/admin/albums/${albumId}/teasers/${teaserId}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,10 @@ import {
|
||||||
Tag, FolderOpen, Save
|
Tag, FolderOpen, Save
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import Toast from '../../../components/Toast';
|
import Toast from '../../../components/Toast';
|
||||||
|
import * as authApi from '../../../api/admin/auth';
|
||||||
|
import { getAlbum } from '../../../api/public/albums';
|
||||||
|
import { getMembers } from '../../../api/public/members';
|
||||||
|
import * as albumsApi from '../../../api/admin/albums';
|
||||||
|
|
||||||
function AdminAlbumPhotos() {
|
function AdminAlbumPhotos() {
|
||||||
const { albumId } = useParams();
|
const { albumId } = useParams();
|
||||||
|
|
@ -145,67 +149,47 @@ function AdminAlbumPhotos() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 로그인 확인
|
// 로그인 확인
|
||||||
const token = localStorage.getItem('adminToken');
|
if (!authApi.hasToken()) {
|
||||||
const userData = localStorage.getItem('adminUser');
|
|
||||||
|
|
||||||
if (!token || !userData) {
|
|
||||||
navigate('/admin');
|
navigate('/admin');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setUser(JSON.parse(userData));
|
setUser(authApi.getCurrentUser());
|
||||||
fetchAlbumData();
|
fetchAlbumData();
|
||||||
}, [navigate, albumId]);
|
}, [navigate, albumId]);
|
||||||
|
|
||||||
const fetchAlbumData = async () => {
|
const fetchAlbumData = async () => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('adminToken');
|
|
||||||
|
|
||||||
// 앨범 정보 로드
|
// 앨범 정보 로드
|
||||||
const albumRes = await fetch(`/api/albums/${albumId}`);
|
const albumData = await getAlbum(albumId);
|
||||||
if (!albumRes.ok) throw new Error('앨범을 찾을 수 없습니다');
|
|
||||||
const albumData = await albumRes.json();
|
|
||||||
setAlbum(albumData);
|
setAlbum(albumData);
|
||||||
|
|
||||||
// 멤버 목록 로드
|
// 멤버 목록 로드
|
||||||
const membersRes = await fetch('/api/members');
|
try {
|
||||||
if (membersRes.ok) {
|
const membersData = await getMembers();
|
||||||
const membersData = await membersRes.json();
|
|
||||||
setMembers(membersData);
|
setMembers(membersData);
|
||||||
}
|
} catch (e) { /* 무시 */ }
|
||||||
|
|
||||||
// 기존 컨셉 포토 목록 로드
|
// 기존 컨셉 포토 목록 로드
|
||||||
const photosRes = await fetch(`/api/admin/albums/${albumId}/photos`, {
|
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
|
||||||
});
|
|
||||||
let photosData = [];
|
let photosData = [];
|
||||||
if (photosRes.ok) {
|
try {
|
||||||
photosData = await photosRes.json();
|
photosData = await albumsApi.getAlbumPhotos(albumId);
|
||||||
setPhotos(photosData);
|
setPhotos(photosData);
|
||||||
}
|
} catch (e) { /* 무시 */ }
|
||||||
|
|
||||||
// 티저 이미지 목록 로드
|
// 티저 이미지 목록 로드
|
||||||
const teasersRes = await fetch(`/api/admin/albums/${albumId}/teasers`, {
|
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
|
||||||
});
|
|
||||||
let teasersData = [];
|
let teasersData = [];
|
||||||
if (teasersRes.ok) {
|
try {
|
||||||
teasersData = await teasersRes.json();
|
teasersData = await albumsApi.getAlbumTeasers(albumId);
|
||||||
setTeasers(teasersData);
|
setTeasers(teasersData);
|
||||||
}
|
} catch (e) { /* 무시 */ }
|
||||||
|
|
||||||
// 시작 번호 자동 설정 (현재 선택된 타입의 마지막 + 1)
|
// 시작 번호 자동 설정
|
||||||
// 컨셉 포토는 컨셉 포토끼리, 티저는 티저끼리 번호 계산
|
|
||||||
const maxPhotoOrder = photosData.length > 0
|
const maxPhotoOrder = photosData.length > 0
|
||||||
? Math.max(...photosData.map(p => p.sort_order || 0))
|
? Math.max(...photosData.map(p => p.sort_order || 0))
|
||||||
: 0;
|
: 0;
|
||||||
const maxTeaserOrder = teasersData.length > 0
|
|
||||||
? Math.max(...teasersData.map(t => t.sort_order || 0))
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
// 기본값은 컨셉 포토 기준
|
|
||||||
setStartNumber(maxPhotoOrder + 1);
|
setStartNumber(maxPhotoOrder + 1);
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('앨범 로드 오류:', error);
|
console.error('앨범 로드 오류:', error);
|
||||||
|
|
@ -230,8 +214,7 @@ function AdminAlbumPhotos() {
|
||||||
}, [photoType, photos, teasers]);
|
}, [photoType, photos, teasers]);
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
localStorage.removeItem('adminToken');
|
authApi.logout();
|
||||||
localStorage.removeItem('adminUser');
|
|
||||||
navigate('/admin');
|
navigate('/admin');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -504,7 +487,6 @@ function AdminAlbumPhotos() {
|
||||||
// 삭제 처리 (기존 사진/티저)
|
// 삭제 처리 (기존 사진/티저)
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
setDeleting(true);
|
setDeleting(true);
|
||||||
const token = localStorage.getItem('adminToken');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 사진 ID와 티저 ID 분리
|
// 사진 ID와 티저 ID 분리
|
||||||
|
|
@ -515,24 +497,12 @@ function AdminAlbumPhotos() {
|
||||||
|
|
||||||
// 사진 삭제
|
// 사진 삭제
|
||||||
for (const photoId of photoIds) {
|
for (const photoId of photoIds) {
|
||||||
const res = await fetch(`/api/admin/albums/${albumId}/photos/${photoId}`, {
|
await albumsApi.deleteAlbumPhoto(albumId, photoId);
|
||||||
method: 'DELETE',
|
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error('사진 삭제 실패');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 티저 삭제
|
// 티저 삭제
|
||||||
for (const teaserId of teaserIds) {
|
for (const teaserId of teaserIds) {
|
||||||
const res = await fetch(`/api/admin/albums/${albumId}/teasers/${teaserId}`, {
|
await albumsApi.deleteAlbumTeaser(albumId, teaserId);
|
||||||
method: 'DELETE',
|
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error('티저 삭제 실패');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI 상태 업데이트
|
// UI 상태 업데이트
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import {
|
||||||
Home, ChevronRight, ChevronLeft, ChevronDown, User, Instagram, Calendar, Briefcase
|
Home, ChevronRight, ChevronLeft, ChevronDown, User, Instagram, Calendar, Briefcase
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import Toast from '../../../components/Toast';
|
import Toast from '../../../components/Toast';
|
||||||
|
import * as authApi from '../../../api/admin/auth';
|
||||||
|
import * as membersApi from '../../../api/admin/members';
|
||||||
|
|
||||||
// 커스텀 데이트픽커 컴포넌트
|
// 커스텀 데이트픽커 컴포넌트
|
||||||
function CustomDatePicker({ value, onChange }) {
|
function CustomDatePicker({ value, onChange }) {
|
||||||
|
|
@ -272,28 +274,18 @@ function AdminMemberEdit() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 로그인 확인
|
// 로그인 확인
|
||||||
const token = localStorage.getItem('adminToken');
|
if (!authApi.hasToken()) {
|
||||||
const userData = localStorage.getItem('adminUser');
|
|
||||||
|
|
||||||
if (!token || !userData) {
|
|
||||||
navigate('/admin');
|
navigate('/admin');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setUser(JSON.parse(userData));
|
setUser(authApi.getCurrentUser());
|
||||||
fetchMember();
|
fetchMember();
|
||||||
}, [navigate, name]);
|
}, [navigate, name]);
|
||||||
|
|
||||||
const fetchMember = async () => {
|
const fetchMember = async () => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('adminToken');
|
const data = await membersApi.getMember(encodeURIComponent(name));
|
||||||
const res = await fetch(`/api/admin/members/${encodeURIComponent(name)}`, {
|
|
||||||
headers: { Authorization: `Bearer ${token}` }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) throw new Error('멤버 조회 실패');
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
setFormData({
|
setFormData({
|
||||||
name: data.name || '',
|
name: data.name || '',
|
||||||
birth_date: data.birth_date ? data.birth_date.split('T')[0] : '',
|
birth_date: data.birth_date ? data.birth_date.split('T')[0] : '',
|
||||||
|
|
@ -311,8 +303,7 @@ function AdminMemberEdit() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
localStorage.removeItem('adminToken');
|
authApi.logout();
|
||||||
localStorage.removeItem('adminUser');
|
|
||||||
navigate('/admin');
|
navigate('/admin');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -331,7 +322,6 @@ function AdminMemberEdit() {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('adminToken');
|
|
||||||
const formDataToSend = new FormData();
|
const formDataToSend = new FormData();
|
||||||
|
|
||||||
formDataToSend.append('name', formData.name);
|
formDataToSend.append('name', formData.name);
|
||||||
|
|
@ -344,14 +334,7 @@ function AdminMemberEdit() {
|
||||||
formDataToSend.append('image', imageFile);
|
formDataToSend.append('image', imageFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(`/api/admin/members/${encodeURIComponent(name)}`, {
|
await membersApi.updateMember(encodeURIComponent(name), formDataToSend);
|
||||||
method: 'PUT',
|
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
|
||||||
body: formDataToSend
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) throw new Error('수정 실패');
|
|
||||||
|
|
||||||
setToast({ message: '멤버 정보가 수정되었습니다.', type: 'success' });
|
setToast({ message: '멤버 정보가 수정되었습니다.', type: 'success' });
|
||||||
setTimeout(() => navigate('/admin/members'), 1000);
|
setTimeout(() => navigate('/admin/members'), 1000);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,10 @@ import {
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Toast from "../../../components/Toast";
|
import Toast from "../../../components/Toast";
|
||||||
import Lightbox from "../../../components/common/Lightbox";
|
import Lightbox from "../../../components/common/Lightbox";
|
||||||
|
import * as authApi from "../../../api/admin/auth";
|
||||||
|
import * as categoriesApi from "../../../api/admin/categories";
|
||||||
|
import * as schedulesApi from "../../../api/admin/schedules";
|
||||||
|
import { getMembers } from "../../../api/public/members";
|
||||||
// 커스텀 데이트픽커 컴포넌트 (AdminMemberEdit.jsx에서 가져옴)
|
// 커스텀 데이트픽커 컴포넌트 (AdminMemberEdit.jsx에서 가져옴)
|
||||||
function CustomDatePicker({ value, onChange, placeholder = "날짜 선택" }) {
|
function CustomDatePicker({ value, onChange, placeholder = "날짜 선택" }) {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
@ -840,8 +844,7 @@ function AdminScheduleForm() {
|
||||||
// 카테고리 로드
|
// 카테고리 로드
|
||||||
const fetchCategories = async () => {
|
const fetchCategories = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/admin/schedule-categories");
|
const data = await categoriesApi.getCategories();
|
||||||
const data = await res.json();
|
|
||||||
setCategories(data);
|
setCategories(data);
|
||||||
// 첫 번째 카테고리를 기본값으로 설정
|
// 첫 번째 카테고리를 기본값으로 설정
|
||||||
if (data.length > 0 && !formData.category) {
|
if (data.length > 0 && !formData.category) {
|
||||||
|
|
@ -861,15 +864,12 @@ function AdminScheduleForm() {
|
||||||
}, [toast]);
|
}, [toast]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = localStorage.getItem("adminToken");
|
if (!authApi.hasToken()) {
|
||||||
const userData = localStorage.getItem("adminUser");
|
|
||||||
|
|
||||||
if (!token || !userData) {
|
|
||||||
navigate("/admin");
|
navigate("/admin");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setUser(JSON.parse(userData));
|
setUser(authApi.getCurrentUser());
|
||||||
fetchMembers();
|
fetchMembers();
|
||||||
fetchCategories();
|
fetchCategories();
|
||||||
|
|
||||||
|
|
@ -883,18 +883,7 @@ function AdminScheduleForm() {
|
||||||
const fetchSchedule = async () => {
|
const fetchSchedule = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem("adminToken");
|
const data = await schedulesApi.getSchedule(id);
|
||||||
const res = await fetch(`/api/admin/schedules/${id}`, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error("일정을 찾을 수 없습니다.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
// 폼 데이터 설정
|
// 폼 데이터 설정
|
||||||
setFormData({
|
setFormData({
|
||||||
|
|
@ -957,8 +946,7 @@ function AdminScheduleForm() {
|
||||||
|
|
||||||
const fetchMembers = async () => {
|
const fetchMembers = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/members");
|
const data = await getMembers();
|
||||||
const data = await res.json();
|
|
||||||
setMembers(data.filter((m) => !m.is_former));
|
setMembers(data.filter((m) => !m.is_former));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("멤버 로드 오류:", error);
|
console.error("멤버 로드 오류:", error);
|
||||||
|
|
@ -966,8 +954,7 @@ function AdminScheduleForm() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
localStorage.removeItem("adminToken");
|
authApi.logout();
|
||||||
localStorage.removeItem("adminUser");
|
|
||||||
navigate("/admin");
|
navigate("/admin");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue