From 1bb52f58d500ddd2480c28e308b2c55642ccc151 Mon Sep 17 00:00:00 2001 From: caadiq Date: Mon, 22 Dec 2025 15:30:09 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B4=80=EB=A6=AC=EC=9E=90=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=83=AD=20UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 탭 UI (콘솔/플레이어/설정) - 콘솔: 로그 영역 + 명령어 입력 + 로그 파일 목록 - 플레이어: 전신 아바타 + 필터 + 킥/밴/OP - 설정: 게임규칙 토글 + 난이도 + 시간 + 날씨 - 더미 데이터로 UI 미리보기 --- frontend/src/pages/Admin.jsx | 624 +++++++++++++++++++++++++++++------ 1 file changed, 526 insertions(+), 98 deletions(-) diff --git a/frontend/src/pages/Admin.jsx b/frontend/src/pages/Admin.jsx index b6279a8..306b895 100644 --- a/frontend/src/pages/Admin.jsx +++ b/frontend/src/pages/Admin.jsx @@ -1,18 +1,83 @@ /** * 관리자 페이지 + * - 탭 UI: 콘솔 / 플레이어 / 설정 */ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; import { useNavigate, useLocation, Link } from 'react-router-dom'; import { useAuth } from '../contexts/AuthContext'; -import { Shield, ArrowLeft, Settings, Server, Users, Loader2, User, Terminal, MessageSquare } from 'lucide-react'; +import { + Shield, ArrowLeft, Loader2, Terminal, Users, Settings, + Send, Ban, UserX, Crown, Sun, Moon, Cloud, CloudRain, CloudLightning, + ChevronDown, FileText, Download, Trash2, Check +} from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; +// 더미 로그 데이터 +const DUMMY_LOGS = [ + { time: '15:01:23', type: 'info', message: '[Server] Starting minecraft server version 1.21.1' }, + { time: '15:01:24', type: 'info', message: '[Server] Loading properties' }, + { time: '15:01:25', type: 'info', message: '[Server] Preparing level "world"' }, + { time: '15:01:28', type: 'info', message: '[Server] Done (3.245s)! For help, type "help"' }, + { time: '15:05:12', type: 'info', message: '[Server] 비머[/127.0.0.1:54321] logged in' }, + { time: '15:05:15', type: 'info', message: '[Server] 비머 joined the game' }, + { time: '15:10:30', type: 'warning', message: '[Server] Can\'t keep up! Is the server overloaded?' }, + { time: '15:15:00', type: 'info', message: '[Server] 비머부캐 joined the game' }, +]; + +// 더미 로그 파일 데이터 +const DUMMY_LOG_FILES = [ + { name: '2024-12-22.log', size: '2.4 MB', date: '2024-12-22' }, + { name: '2024-12-21.log', size: '1.8 MB', date: '2024-12-21' }, + { name: '2024-12-20.log', size: '3.1 MB', date: '2024-12-20' }, +]; + +// 더미 플레이어 데이터 +const DUMMY_PLAYERS = [ + { uuid: '1234-5678-9012-3456', name: '비머', isOnline: true, isOp: true }, + { uuid: '2345-6789-0123-4567', name: '비머부캐', isOnline: true, isOp: false }, + { uuid: '3456-7890-1234-5678', name: 'Steve', isOnline: false, isOp: false }, + { uuid: '4567-8901-2345-6789', name: 'Alex', isOnline: false, isOp: false }, +]; + +// 더미 게임규칙 데이터 +const DUMMY_GAMERULES = [ + { name: 'keepInventory', value: false, label: '인벤토리 유지' }, + { name: 'doDaylightCycle', value: true, label: '낮/밤 주기' }, + { name: 'doMobSpawning', value: true, label: '몹 스폰' }, + { name: 'doFireTick', value: true, label: '불 번짐' }, + { name: 'mobGriefing', value: true, label: '몹 그리핑' }, + { name: 'pvp', value: true, label: 'PvP' }, +]; + export default function Admin({ isMobile = false }) { const { isLoggedIn, isAdmin, user, loading } = useAuth(); const navigate = useNavigate(); const location = useLocation(); const [toast, setToast] = useState(null); + + // 탭 상태 + const [activeTab, setActiveTab] = useState('console'); + + // 콘솔 관련 상태 + const [logs, setLogs] = useState(DUMMY_LOGS); + const [command, setCommand] = useState(''); + const [logFiles] = useState(DUMMY_LOG_FILES); + const logEndRef = useRef(null); + + // 플레이어 관련 상태 + const [players, setPlayers] = useState(DUMMY_PLAYERS); + const [playerFilter, setPlayerFilter] = useState('all'); // all, online, offline, banned + const [selectedPlayer, setSelectedPlayer] = useState(null); + const [showPlayerDialog, setShowPlayerDialog] = useState(false); + const [dialogAction, setDialogAction] = useState(null); // kick, ban, op + const [actionReason, setActionReason] = useState(''); + + // 설정 관련 상태 + const [gamerules, setGamerules] = useState(DUMMY_GAMERULES); + const [difficulty, setDifficulty] = useState('normal'); + const [timeOfDay, setTimeOfDay] = useState('day'); + const [weather, setWeather] = useState('clear'); // 권한 확인 useEffect(() => { @@ -21,9 +86,7 @@ export default function Admin({ isMobile = false }) { navigate('/login', { state: { from: location.pathname } }); } else if (!isAdmin) { setToast('관리자 권한이 필요합니다.'); - setTimeout(() => { - navigate('/'); - }, 1500); + setTimeout(() => navigate('/'), 1500); } } }, [isLoggedIn, isAdmin, loading, navigate, location.pathname]); @@ -36,6 +99,78 @@ export default function Admin({ isMobile = false }) { } }, [toast]); + // 로그 스크롤 + useEffect(() => { + logEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [logs]); + + // 명령어 실행 (더미) + const handleCommand = () => { + if (!command.trim()) return; + + const newLogs = [ + { time: new Date().toLocaleTimeString('ko-KR', { hour12: false }), type: 'command', message: `> ${command}` }, + { time: new Date().toLocaleTimeString('ko-KR', { hour12: false }), type: 'info', message: `[Server] 명령어 실행됨: ${command}` } + ]; + + setLogs(prev => [...prev, ...newLogs]); + setCommand(''); + setToast('명령어가 실행되었습니다.'); + }; + + // 플레이어 액션 핸들러 + const handlePlayerAction = () => { + if (!selectedPlayer || !dialogAction) return; + + let message = ''; + switch (dialogAction) { + case 'kick': + message = `${selectedPlayer.name}님을 추방했습니다.`; + break; + case 'ban': + message = `${selectedPlayer.name}님을 차단했습니다.`; + break; + case 'op': + const isOp = players.find(p => p.uuid === selectedPlayer.uuid)?.isOp; + setPlayers(prev => prev.map(p => + p.uuid === selectedPlayer.uuid ? { ...p, isOp: !isOp } : p + )); + message = isOp ? `${selectedPlayer.name}님의 OP를 해제했습니다.` : `${selectedPlayer.name}님에게 OP를 부여했습니다.`; + break; + } + + setShowPlayerDialog(false); + setSelectedPlayer(null); + setDialogAction(null); + setActionReason(''); + setToast(message); + }; + + // 게임규칙 토글 + const toggleGamerule = (name) => { + setGamerules(prev => prev.map(rule => + rule.name === name ? { ...rule, value: !rule.value } : rule + )); + setToast('게임규칙이 변경되었습니다.'); + }; + + // 로그 색상 + const getLogColor = (type) => { + switch (type) { + case 'error': return 'text-red-400'; + case 'warning': return 'text-yellow-400'; + case 'command': return 'text-mc-green'; + default: return 'text-zinc-300'; + } + }; + + // 필터된 플레이어 + const filteredPlayers = players.filter(p => { + if (playerFilter === 'online') return p.isOnline; + if (playerFilter === 'offline') return !p.isOnline; + return true; + }); + if (loading) { return (
@@ -47,7 +182,6 @@ export default function Admin({ isMobile = false }) { if (!isLoggedIn || !isAdmin) { return ( <> - {/* 토스트 알림 */} {toast && ( + {/* 토스트 */} + + {toast && ( + + {toast} + + )} + + {/* 모바일용 헤더 */} {isMobile && (
- +

관리자

-

서버 관리 및 설정

)} -
+
{/* 데스크탑용 타이틀 */} {!isMobile && (
@@ -96,100 +247,377 @@ export default function Admin({ isMobile = false }) {
)} - {/* 관리자 정보 카드 */} -
-

- - 관리자 정보 -

- -
-
- -
- -
-
-

닉네임

-

{user?.name || '-'}

-
-
-

이메일

-

{user?.email}

-
-
-
-
+ {/* 탭 네비게이션 */} +
+ {tabs.map(tab => ( + + ))} +
- {/* 서버 관리 섹션 */} -
-

- - 서버 관리 -

- -
- {/* 서버 명령어 */} -
-
- -

서버 명령어

+ {/* 탭 콘텐츠 */} + + {/* 콘솔 탭 */} + {activeTab === 'console' && ( + + {/* 로그 영역 */} +
+
+ {logs.map((log, index) => ( +
+ [{log.time}] {log.message} +
+ ))} +
+
+ + {/* 명령어 입력 */} +
+
+ {'>'} + setCommand(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleCommand()} + placeholder="명령어 입력..." + className="flex-1 bg-transparent py-3 text-white placeholder-zinc-500 focus:outline-none font-mono" + /> +
+ +
-

- 마인크래프트 서버에 명령어를 전송합니다. -

-
- 추후 업데이트 예정 -
-
- {/* 공지 전송 */} -
-
- -

공지 전송

+ {/* 로그 파일 목록 */} +
+

+ + 로그 파일 +

+
+ {logFiles.map((file, index) => ( +
+
+

{file.name}

+

{file.size}

+
+
+ + +
+
+ ))} +
-

- 게임 내 모든 플레이어에게 공지를 전송합니다. -

-
- 추후 업데이트 예정 + + )} + + {/* 플레이어 탭 */} + {activeTab === 'players' && ( + + {/* 필터 */} +
+ {[ + { id: 'all', label: '전체' }, + { id: 'online', label: '온라인' }, + { id: 'offline', label: '오프라인' }, + ].map(filter => ( + + ))}
-
-
-
- {/* 플레이어 관리 섹션 */} -
-

- - 플레이어 관리 -

- -

- 접속 중인 플레이어 목록 및 관리 기능 -

-
- 추후 업데이트 예정 -
-
+ {/* 플레이어 그리드 */} +
+ {filteredPlayers.map((player) => ( +
+ {/* OP 뱃지 */} + {player.isOp && ( +
+ +
+ )} + + {/* 온/오프라인 표시 */} +
+ + {/* 전신 아바타 */} + {player.name} + +

{player.name}

+ + {/* 액션 버튼 */} +
+ + {player.isOnline && ( + + )} + +
+
+ ))} +
+ + )} - {/* 설정 섹션 */} -
-

- - 설정 -

- -

- 대시보드 설정 및 구성 관리 -

-
- 추후 업데이트 예정 -
-
+ {/* 설정 탭 */} + {activeTab === 'settings' && ( + + {/* 게임규칙 */} +
+

🎮 게임규칙

+
+ {gamerules.map(rule => ( + + ))} +
+
+ + {/* 난이도 */} +
+

⚔️ 난이도

+
+ {[ + { id: 'peaceful', label: '평화로움' }, + { id: 'easy', label: '쉬움' }, + { id: 'normal', label: '보통' }, + { id: 'hard', label: '어려움' }, + ].map(d => ( + + ))} +
+
+ + {/* 시간 */} +
+

🕐 시간

+
+ {[ + { id: 'day', label: '아침', icon: Sun }, + { id: 'noon', label: '낮', icon: Sun }, + { id: 'night', label: '밤', icon: Moon }, + ].map(t => ( + + ))} +
+
+ + {/* 날씨 */} +
+

🌤️ 날씨

+
+ {[ + { id: 'clear', label: '맑음', icon: Sun }, + { id: 'rain', label: '비', icon: CloudRain }, + { id: 'thunder', label: '천둥', icon: CloudLightning }, + ].map(w => ( + + ))} +
+
+
+ )} +
+ + {/* 플레이어 액션 다이얼로그 */} + + {showPlayerDialog && selectedPlayer && ( + setShowPlayerDialog(false)} + > + e.stopPropagation()} + > +

+ {dialogAction === 'kick' && <> 플레이어 킥} + {dialogAction === 'ban' && <> 플레이어 밴} + {dialogAction === 'op' && <> OP {players.find(p => p.uuid === selectedPlayer.uuid)?.isOp ? '해제' : '부여'}} +

+ +
+ {selectedPlayer.name} +
+

{selectedPlayer.name}

+

{selectedPlayer.uuid}

+
+
+ + {(dialogAction === 'kick' || dialogAction === 'ban') && ( + setActionReason(e.target.value)} + placeholder="사유 (선택)" + className="w-full bg-zinc-800 rounded-xl p-3 text-white placeholder-zinc-500 focus:outline-none focus:ring-2 focus:ring-mc-green/50 mb-4" + /> + )} + +
+ + +
+
+
+ )} +
); } -