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줄의 중복 코드 제거
This commit is contained in:
parent
7f9c53b53a
commit
97fb4b7964
9 changed files with 26 additions and 280 deletions
|
|
@ -2,11 +2,12 @@ import { useState, useEffect, useRef } from 'react';
|
||||||
import { useNavigate, useParams, Link } from 'react-router-dom';
|
import { useNavigate, useParams, Link } from 'react-router-dom';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import {
|
import {
|
||||||
Save, Home, ChevronRight, LogOut, Music, Trash2, Plus, Image, Star,
|
Save, Home, ChevronRight, Music, Trash2, Plus, Image, Star,
|
||||||
ChevronDown
|
ChevronDown
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import Toast from '../../../components/Toast';
|
import Toast from '../../../components/Toast';
|
||||||
import CustomDatePicker from '../../../components/admin/CustomDatePicker';
|
import CustomDatePicker from '../../../components/admin/CustomDatePicker';
|
||||||
|
import AdminHeader from '../../../components/admin/AdminHeader';
|
||||||
import useToast from '../../../hooks/useToast';
|
import useToast from '../../../hooks/useToast';
|
||||||
|
|
||||||
// 커스텀 드롭다운 컴포넌트
|
// 커스텀 드롭다운 컴포넌트
|
||||||
|
|
@ -140,12 +141,6 @@ function AdminAlbumForm() {
|
||||||
}
|
}
|
||||||
}, [id, isEditMode, navigate]);
|
}, [id, isEditMode, navigate]);
|
||||||
|
|
||||||
const handleLogout = () => {
|
|
||||||
localStorage.removeItem('adminToken');
|
|
||||||
localStorage.removeItem('adminUser');
|
|
||||||
navigate('/admin');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInputChange = (e) => {
|
const handleInputChange = (e) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
|
|
||||||
|
|
@ -283,30 +278,7 @@ function AdminAlbumForm() {
|
||||||
<Toast toast={toast} onClose={() => setToast(null)} />
|
<Toast toast={toast} onClose={() => setToast(null)} />
|
||||||
|
|
||||||
{/* 헤더 */}
|
{/* 헤더 */}
|
||||||
<header className="bg-white shadow-sm border-b border-gray-100">
|
<AdminHeader user={user} />
|
||||||
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Link to="/admin/dashboard" className="text-2xl font-bold text-primary hover:opacity-80 transition-opacity">
|
|
||||||
fromis_9
|
|
||||||
</Link>
|
|
||||||
<span className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full">
|
|
||||||
Admin
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<span className="text-gray-500 text-sm">
|
|
||||||
안녕하세요, <span className="text-gray-900 font-medium">{user?.username}</span>님
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={handleLogout}
|
|
||||||
className="flex items-center gap-2 px-4 py-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
<LogOut size={18} />
|
|
||||||
<span>로그아웃</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{/* 메인 콘텐츠 */}
|
{/* 메인 콘텐츠 */}
|
||||||
<main className="max-w-4xl mx-auto px-6 py-8">
|
<main className="max-w-4xl mx-auto px-6 py-8">
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,12 @@ import { useNavigate, Link, useParams } from 'react-router-dom';
|
||||||
import { motion, AnimatePresence, Reorder } from 'framer-motion';
|
import { motion, AnimatePresence, Reorder } from 'framer-motion';
|
||||||
import {
|
import {
|
||||||
Upload, Trash2, Image, X, Check, Plus,
|
Upload, Trash2, Image, X, Check, Plus,
|
||||||
Home, ChevronRight, LogOut, ArrowLeft, Grid, List,
|
Home, ChevronRight, ArrowLeft, Grid, List,
|
||||||
ZoomIn, AlertTriangle, GripVertical, Users, User, Users2,
|
ZoomIn, AlertTriangle, GripVertical, Users, User, Users2,
|
||||||
Tag, FolderOpen, Save
|
Tag, FolderOpen, Save
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import Toast from '../../../components/Toast';
|
import Toast from '../../../components/Toast';
|
||||||
|
import AdminHeader from '../../../components/admin/AdminHeader';
|
||||||
import useToast from '../../../hooks/useToast';
|
import useToast from '../../../hooks/useToast';
|
||||||
import * as authApi from '../../../api/admin/auth';
|
import * as authApi from '../../../api/admin/auth';
|
||||||
import { getAlbum } from '../../../api/public/albums';
|
import { getAlbum } from '../../../api/public/albums';
|
||||||
|
|
@ -206,11 +207,6 @@ function AdminAlbumPhotos() {
|
||||||
}
|
}
|
||||||
}, [photoType, photos, teasers]);
|
}, [photoType, photos, teasers]);
|
||||||
|
|
||||||
const handleLogout = () => {
|
|
||||||
authApi.logout();
|
|
||||||
navigate('/admin');
|
|
||||||
};
|
|
||||||
|
|
||||||
// 파일 선택
|
// 파일 선택
|
||||||
const handleFileSelect = (e) => {
|
const handleFileSelect = (e) => {
|
||||||
const files = Array.from(e.target.files);
|
const files = Array.from(e.target.files);
|
||||||
|
|
@ -631,30 +627,7 @@ function AdminAlbumPhotos() {
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
{/* 헤더 */}
|
{/* 헤더 */}
|
||||||
<header className="bg-white shadow-sm border-b border-gray-100">
|
<AdminHeader user={user} />
|
||||||
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Link to="/admin/dashboard" className="text-2xl font-bold text-primary hover:opacity-80 transition-opacity">
|
|
||||||
fromis_9
|
|
||||||
</Link>
|
|
||||||
<span className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full">
|
|
||||||
Admin
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<span className="text-gray-500 text-sm">
|
|
||||||
안녕하세요, <span className="text-gray-900 font-medium">{user?.username}</span>님
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={handleLogout}
|
|
||||||
className="flex items-center gap-2 px-4 py-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
<LogOut size={18} />
|
|
||||||
<span>로그아웃</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{/* 메인 콘텐츠 */}
|
{/* 메인 콘텐츠 */}
|
||||||
<main className="max-w-7xl mx-auto px-6 py-8">
|
<main className="max-w-7xl mx-auto px-6 py-8">
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,11 @@ import { useNavigate, Link } from 'react-router-dom';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import {
|
import {
|
||||||
Plus, Search, Edit2, Trash2, Image, Music,
|
Plus, Search, Edit2, Trash2, Image, Music,
|
||||||
Home, ChevronRight, LogOut, Calendar, AlertTriangle, X
|
Home, ChevronRight, Calendar, AlertTriangle, X
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import Toast from '../../../components/Toast';
|
import Toast from '../../../components/Toast';
|
||||||
import Tooltip from '../../../components/Tooltip';
|
import Tooltip from '../../../components/Tooltip';
|
||||||
|
import AdminHeader from '../../../components/admin/AdminHeader';
|
||||||
import useToast from '../../../hooks/useToast';
|
import useToast from '../../../hooks/useToast';
|
||||||
import * as authApi from '../../../api/admin/auth';
|
import * as authApi from '../../../api/admin/auth';
|
||||||
import { getAlbums } from '../../../api/public/albums';
|
import { getAlbums } from '../../../api/public/albums';
|
||||||
|
|
@ -44,11 +45,6 @@ function AdminAlbums() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogout = () => {
|
|
||||||
authApi.logout();
|
|
||||||
navigate('/admin');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
if (!deleteDialog.album) return;
|
if (!deleteDialog.album) return;
|
||||||
|
|
||||||
|
|
@ -145,30 +141,7 @@ function AdminAlbums() {
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
{/* 헤더 */}
|
{/* 헤더 */}
|
||||||
<header className="bg-white shadow-sm border-b border-gray-100">
|
<AdminHeader user={user} />
|
||||||
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Link to="/admin/dashboard" className="text-2xl font-bold text-primary hover:opacity-80 transition-opacity">
|
|
||||||
fromis_9
|
|
||||||
</Link>
|
|
||||||
<span className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full">
|
|
||||||
Admin
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<span className="text-gray-500 text-sm">
|
|
||||||
안녕하세요, <span className="text-gray-900 font-medium">{user?.username}</span>님
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={handleLogout}
|
|
||||||
className="flex items-center gap-2 px-4 py-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
<LogOut size={18} />
|
|
||||||
<span>로그아웃</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{/* 메인 콘텐츠 */}
|
{/* 메인 콘텐츠 */}
|
||||||
<main className="max-w-7xl mx-auto px-6 py-8">
|
<main className="max-w-7xl mx-auto px-6 py-8">
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@ import { useState, useEffect } from 'react';
|
||||||
import { useNavigate, Link } from 'react-router-dom';
|
import { useNavigate, Link } from 'react-router-dom';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import {
|
import {
|
||||||
Disc3, Calendar, Users, LogOut,
|
Disc3, Calendar, Users,
|
||||||
Home, ChevronRight
|
Home, ChevronRight
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
import AdminHeader from '../../../components/admin/AdminHeader';
|
||||||
import * as authApi from '../../../api/admin/auth';
|
import * as authApi from '../../../api/admin/auth';
|
||||||
import { getMembers } from '../../../api/public/members';
|
import { getMembers } from '../../../api/public/members';
|
||||||
import { getAlbums, getAlbum } from '../../../api/public/albums';
|
import { getAlbums, getAlbum } from '../../../api/public/albums';
|
||||||
|
|
@ -107,11 +108,6 @@ function AdminDashboard() {
|
||||||
} catch (e) { console.error('일정 통계 오류:', e); }
|
} catch (e) { console.error('일정 통계 오류:', e); }
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogout = () => {
|
|
||||||
authApi.logout();
|
|
||||||
navigate('/admin');
|
|
||||||
};
|
|
||||||
|
|
||||||
// 메뉴 아이템
|
// 메뉴 아이템
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{
|
{
|
||||||
|
|
@ -140,30 +136,7 @@ function AdminDashboard() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50">
|
||||||
{/* 헤더 */}
|
{/* 헤더 */}
|
||||||
<header className="bg-white shadow-sm border-b border-gray-100">
|
<AdminHeader user={user} />
|
||||||
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Link to="/admin/dashboard" className="text-2xl font-bold text-primary hover:opacity-80 transition-opacity">
|
|
||||||
fromis_9
|
|
||||||
</Link>
|
|
||||||
<span className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full">
|
|
||||||
Admin
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<span className="text-gray-500 text-sm">
|
|
||||||
안녕하세요, <span className="text-gray-900 font-medium">{user?.username}</span>님
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={handleLogout}
|
|
||||||
className="flex items-center gap-2 px-4 py-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
<LogOut size={18} />
|
|
||||||
<span>로그아웃</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{/* 메인 콘텐츠 */}
|
{/* 메인 콘텐츠 */}
|
||||||
<main className="max-w-7xl mx-auto px-6 py-8">
|
<main className="max-w-7xl mx-auto px-6 py-8">
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ import { useState, useEffect, useRef } from 'react';
|
||||||
import { useNavigate, useParams, Link } from 'react-router-dom';
|
import { useNavigate, useParams, Link } from 'react-router-dom';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import {
|
import {
|
||||||
Save, Upload, LogOut,
|
Save, Upload,
|
||||||
Home, ChevronRight, User, Instagram, Calendar, Briefcase
|
Home, ChevronRight, User, Instagram, Calendar, Briefcase
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import Toast from '../../../components/Toast';
|
import Toast from '../../../components/Toast';
|
||||||
import CustomDatePicker from '../../../components/admin/CustomDatePicker';
|
import CustomDatePicker from '../../../components/admin/CustomDatePicker';
|
||||||
|
import AdminHeader from '../../../components/admin/AdminHeader';
|
||||||
import useToast from '../../../hooks/useToast';
|
import useToast from '../../../hooks/useToast';
|
||||||
import * as authApi from '../../../api/admin/auth';
|
import * as authApi from '../../../api/admin/auth';
|
||||||
import * as membersApi from '../../../api/admin/members';
|
import * as membersApi from '../../../api/admin/members';
|
||||||
|
|
@ -59,11 +60,6 @@ function AdminMemberEdit() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogout = () => {
|
|
||||||
authApi.logout();
|
|
||||||
navigate('/admin');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleImageChange = (e) => {
|
const handleImageChange = (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
|
|
@ -108,30 +104,7 @@ function AdminMemberEdit() {
|
||||||
<Toast toast={toast} onClose={() => setToast(null)} />
|
<Toast toast={toast} onClose={() => setToast(null)} />
|
||||||
|
|
||||||
{/* 헤더 */}
|
{/* 헤더 */}
|
||||||
<header className="bg-white shadow-sm border-b border-gray-100">
|
<AdminHeader user={user} />
|
||||||
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Link to="/admin/dashboard" className="text-2xl font-bold text-primary hover:opacity-80 transition-opacity">
|
|
||||||
fromis_9
|
|
||||||
</Link>
|
|
||||||
<span className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full">
|
|
||||||
Admin
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<span className="text-gray-500 text-sm">
|
|
||||||
안녕하세요, <span className="text-gray-900 font-medium">{user?.username}</span>님
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={handleLogout}
|
|
||||||
className="flex items-center gap-2 px-4 py-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
<LogOut size={18} />
|
|
||||||
<span>로그아웃</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{/* 메인 콘텐츠 */}
|
{/* 메인 콘텐츠 */}
|
||||||
<main className="max-w-4xl mx-auto px-6 py-8">
|
<main className="max-w-4xl mx-auto px-6 py-8">
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { useState, useEffect, useRef, useMemo, memo, useDeferredValue } from 're
|
||||||
import { useNavigate, Link } from 'react-router-dom';
|
import { useNavigate, Link } from 'react-router-dom';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import {
|
import {
|
||||||
LogOut, Home, ChevronRight, Calendar, Plus, Edit2, Trash2,
|
Home, ChevronRight, Calendar, Plus, Edit2, Trash2,
|
||||||
ChevronLeft, Search, ChevronDown, AlertTriangle, Bot, Tag, ArrowLeft, ExternalLink, Clock, Link2
|
ChevronLeft, Search, ChevronDown, AlertTriangle, Bot, Tag, ArrowLeft, ExternalLink, Clock, Link2
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
|
|
@ -10,6 +10,7 @@ import { useInView } from 'react-intersection-observer';
|
||||||
|
|
||||||
import Toast from '../../../components/Toast';
|
import Toast from '../../../components/Toast';
|
||||||
import Tooltip from '../../../components/Tooltip';
|
import Tooltip from '../../../components/Tooltip';
|
||||||
|
import AdminHeader from '../../../components/admin/AdminHeader';
|
||||||
import useScheduleStore from '../../../stores/useScheduleStore';
|
import useScheduleStore from '../../../stores/useScheduleStore';
|
||||||
import useToast from '../../../hooks/useToast';
|
import useToast from '../../../hooks/useToast';
|
||||||
import { getTodayKST, formatDate } from '../../../utils/date';
|
import { getTodayKST, formatDate } from '../../../utils/date';
|
||||||
|
|
@ -408,12 +409,6 @@ function AdminSchedule() {
|
||||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||||
}, [showYearMonthPicker, showCategoryTooltip]);
|
}, [showYearMonthPicker, showCategoryTooltip]);
|
||||||
|
|
||||||
const handleLogout = () => {
|
|
||||||
localStorage.removeItem('adminToken');
|
|
||||||
localStorage.removeItem('adminUser');
|
|
||||||
navigate('/admin');
|
|
||||||
};
|
|
||||||
|
|
||||||
// 월 이동
|
// 월 이동
|
||||||
const prevMonth = () => {
|
const prevMonth = () => {
|
||||||
setSlideDirection(-1);
|
setSlideDirection(-1);
|
||||||
|
|
@ -643,30 +638,7 @@ function AdminSchedule() {
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
{/* 헤더 */}
|
{/* 헤더 */}
|
||||||
<header className="bg-white shadow-sm border-b border-gray-100">
|
<AdminHeader user={user} />
|
||||||
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Link to="/admin/dashboard" className="text-2xl font-bold text-primary hover:opacity-80 transition-opacity">
|
|
||||||
fromis_9
|
|
||||||
</Link>
|
|
||||||
<span className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full">
|
|
||||||
Admin
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<span className="text-gray-500 text-sm">
|
|
||||||
안녕하세요, <span className="text-gray-900 font-medium">{user?.username}</span>님
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={handleLogout}
|
|
||||||
className="flex items-center gap-2 px-4 py-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
<LogOut size={18} />
|
|
||||||
<span>로그아웃</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{/* 메인 콘텐츠 */}
|
{/* 메인 콘텐츠 */}
|
||||||
<main className="max-w-7xl mx-auto px-6 py-8">
|
<main className="max-w-7xl mx-auto px-6 py-8">
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ import { useState, useEffect } from 'react';
|
||||||
import { useNavigate, Link } from 'react-router-dom';
|
import { useNavigate, Link } from 'react-router-dom';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import {
|
import {
|
||||||
LogOut, Home, ChevronRight, Bot, Play, Square,
|
Home, ChevronRight, Bot, Play, Square,
|
||||||
Youtube, Calendar, Clock, CheckCircle, XCircle, RefreshCw, Download
|
Youtube, Calendar, Clock, CheckCircle, XCircle, RefreshCw, Download
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import Toast from '../../../components/Toast';
|
import Toast from '../../../components/Toast';
|
||||||
import Tooltip from '../../../components/Tooltip';
|
import Tooltip from '../../../components/Tooltip';
|
||||||
|
import AdminHeader from '../../../components/admin/AdminHeader';
|
||||||
import useToast from '../../../hooks/useToast';
|
import useToast from '../../../hooks/useToast';
|
||||||
import * as botsApi from '../../../api/admin/bots';
|
import * as botsApi from '../../../api/admin/bots';
|
||||||
|
|
||||||
|
|
@ -70,12 +71,6 @@ function AdminScheduleBots() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogout = () => {
|
|
||||||
localStorage.removeItem('adminToken');
|
|
||||||
localStorage.removeItem('adminUser');
|
|
||||||
navigate('/admin');
|
|
||||||
};
|
|
||||||
|
|
||||||
// 봇 시작/정지 토글
|
// 봇 시작/정지 토글
|
||||||
const toggleBot = async (botId, currentStatus, botName) => {
|
const toggleBot = async (botId, currentStatus, botName) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -193,30 +188,7 @@ function AdminScheduleBots() {
|
||||||
<Toast toast={toast} onClose={() => setToast(null)} />
|
<Toast toast={toast} onClose={() => setToast(null)} />
|
||||||
|
|
||||||
{/* 헤더 */}
|
{/* 헤더 */}
|
||||||
<header className="bg-white shadow-sm border-b border-gray-100">
|
<AdminHeader user={user} />
|
||||||
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Link to="/admin/dashboard" className="text-2xl font-bold text-primary hover:opacity-80 transition-opacity">
|
|
||||||
fromis_9
|
|
||||||
</Link>
|
|
||||||
<span className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full">
|
|
||||||
Admin
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<span className="text-gray-500 text-sm">
|
|
||||||
안녕하세요, <span className="text-gray-900 font-medium">{user?.username}</span>님
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={handleLogout}
|
|
||||||
className="flex items-center gap-2 px-4 py-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
<LogOut size={18} />
|
|
||||||
<span>로그아웃</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{/* 메인 콘텐츠 */}
|
{/* 메인 콘텐츠 */}
|
||||||
<main className="max-w-7xl mx-auto px-6 py-8">
|
<main className="max-w-7xl mx-auto px-6 py-8">
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { motion, AnimatePresence, Reorder } from 'framer-motion';
|
import { motion, AnimatePresence, Reorder } from 'framer-motion';
|
||||||
import { LogOut, Home, ChevronRight, Plus, Edit3, Trash2, GripVertical, X, AlertTriangle } from 'lucide-react';
|
import { Home, ChevronRight, Plus, Edit3, Trash2, GripVertical, X, AlertTriangle } from 'lucide-react';
|
||||||
import { HexColorPicker } from 'react-colorful';
|
import { HexColorPicker } from 'react-colorful';
|
||||||
import Toast from '../../../components/Toast';
|
import Toast from '../../../components/Toast';
|
||||||
|
import AdminHeader from '../../../components/admin/AdminHeader';
|
||||||
import useToast from '../../../hooks/useToast';
|
import useToast from '../../../hooks/useToast';
|
||||||
import * as authApi from '../../../api/admin/auth';
|
import * as authApi from '../../../api/admin/auth';
|
||||||
import * as categoriesApi from '../../../api/admin/categories';
|
import * as categoriesApi from '../../../api/admin/categories';
|
||||||
|
|
@ -85,12 +86,6 @@ function AdminScheduleCategory() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 로그아웃
|
|
||||||
const handleLogout = () => {
|
|
||||||
authApi.logout();
|
|
||||||
navigate('/admin');
|
|
||||||
};
|
|
||||||
|
|
||||||
// 모달 열기 (추가/수정)
|
// 모달 열기 (추가/수정)
|
||||||
const openModal = (category = null) => {
|
const openModal = (category = null) => {
|
||||||
if (category) {
|
if (category) {
|
||||||
|
|
@ -189,30 +184,7 @@ function AdminScheduleCategory() {
|
||||||
<Toast toast={toast} onClose={() => setToast(null)} />
|
<Toast toast={toast} onClose={() => setToast(null)} />
|
||||||
|
|
||||||
{/* 헤더 */}
|
{/* 헤더 */}
|
||||||
<header className="bg-white shadow-sm border-b border-gray-100">
|
<AdminHeader user={user} />
|
||||||
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Link to="/admin/dashboard" className="text-2xl font-bold text-primary hover:opacity-80 transition-opacity">
|
|
||||||
fromis_9
|
|
||||||
</Link>
|
|
||||||
<span className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full">
|
|
||||||
Admin
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<span className="text-gray-500 text-sm">
|
|
||||||
안녕하세요, <span className="text-gray-900 font-medium">{user?.username}</span>님
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={handleLogout}
|
|
||||||
className="flex items-center gap-2 px-4 py-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
<LogOut size={18} />
|
|
||||||
로그아웃
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{/* 메인 콘텐츠 */}
|
{/* 메인 콘텐츠 */}
|
||||||
<main className="max-w-4xl mx-auto px-6 py-8">
|
<main className="max-w-4xl mx-auto px-6 py-8">
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import Toast from "../../../components/Toast";
|
||||||
import Lightbox from "../../../components/common/Lightbox";
|
import Lightbox from "../../../components/common/Lightbox";
|
||||||
import CustomDatePicker from "../../../components/admin/CustomDatePicker";
|
import CustomDatePicker from "../../../components/admin/CustomDatePicker";
|
||||||
import CustomTimePicker from "../../../components/admin/CustomTimePicker";
|
import CustomTimePicker from "../../../components/admin/CustomTimePicker";
|
||||||
|
import AdminHeader from "../../../components/admin/AdminHeader";
|
||||||
import useToast from "../../../hooks/useToast";
|
import useToast from "../../../hooks/useToast";
|
||||||
import * as authApi from "../../../api/admin/auth";
|
import * as authApi from "../../../api/admin/auth";
|
||||||
import * as categoriesApi from "../../../api/admin/categories";
|
import * as categoriesApi from "../../../api/admin/categories";
|
||||||
|
|
@ -236,11 +237,6 @@ function AdminScheduleForm() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogout = () => {
|
|
||||||
authApi.logout();
|
|
||||||
navigate("/admin");
|
|
||||||
};
|
|
||||||
|
|
||||||
// 멤버 토글
|
// 멤버 토글
|
||||||
const toggleMember = (memberId) => {
|
const toggleMember = (memberId) => {
|
||||||
const newMembers = formData.members.includes(memberId)
|
const newMembers = formData.members.includes(memberId)
|
||||||
|
|
@ -709,37 +705,7 @@ function AdminScheduleForm() {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 헤더 */}
|
{/* 헤더 */}
|
||||||
<header className="bg-white shadow-sm border-b border-gray-100">
|
<AdminHeader user={user} />
|
||||||
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Link
|
|
||||||
to="/admin/dashboard"
|
|
||||||
className="text-2xl font-bold text-primary hover:opacity-80 transition-opacity"
|
|
||||||
>
|
|
||||||
fromis_9
|
|
||||||
</Link>
|
|
||||||
<span className="px-3 py-1 bg-primary/10 text-primary text-sm font-medium rounded-full">
|
|
||||||
Admin
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<span className="text-gray-500 text-sm">
|
|
||||||
안녕하세요,{" "}
|
|
||||||
<span className="text-gray-900 font-medium">
|
|
||||||
{user?.username}
|
|
||||||
</span>
|
|
||||||
님
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={handleLogout}
|
|
||||||
className="flex items-center gap-2 px-4 py-2 text-gray-500 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
<LogOut size={18} />
|
|
||||||
<span>로그아웃</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{/* 메인 콘텐츠 */}
|
{/* 메인 콘텐츠 */}
|
||||||
<main className="max-w-4xl mx-auto px-6 py-8">
|
<main className="max-w-4xl mx-auto px-6 py-8">
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue