refactor: 모든 페이지의 title 속성을 Tooltip 컴포넌트로 통일

- Admin.jsx: 버튼들의 title을 Tooltip으로 변경 (새로고침, 삭제, 다운로드, OP, 킥, 밴 등)
- PlayerStatsPage.jsx: 아이템/몹 이름에 Tooltip 적용
- ServerDetail.jsx: 적용된 모드 목록에 Tooltip 적용
This commit is contained in:
caadiq 2025-12-24 16:30:53 +09:00
parent 01aa85f041
commit 3661de82ca
3 changed files with 142 additions and 122 deletions

View file

@ -1239,16 +1239,17 @@ export default function Admin({ isMobile = false }) {
{/* 맨 아래로 스크롤 버튼 */}
<AnimatePresence>
{!isAtBottom && logs.length > 0 && (
<motion.button
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
onClick={scrollToBottom}
className="absolute bottom-[88px] right-4 p-3 bg-mc-green hover:bg-mc-green/80 text-white rounded-full shadow-xl shadow-black/50 transition-colors z-10"
title="맨 아래로 이동"
>
<ArrowDown size={20} />
</motion.button>
<Tooltip content="맨 아래로 이동">
<motion.button
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
onClick={scrollToBottom}
className="absolute bottom-[88px] right-4 p-3 bg-mc-green hover:bg-mc-green/80 text-white rounded-full shadow-xl shadow-black/50 transition-colors z-10"
>
<ArrowDown size={20} />
</motion.button>
</Tooltip>
)}
</AnimatePresence>
@ -1318,13 +1319,14 @@ export default function Admin({ isMobile = false }) {
로그 파일
{logFiles.length > 0 && <span className="text-xs text-zinc-500">({logFiles.length})</span>}
</h3>
<button
onClick={fetchLogFiles}
className="p-2 text-zinc-400 hover:text-mc-green hover:bg-zinc-800 rounded-lg transition-all hover:rotate-180 duration-300"
title="새로고침"
>
<RefreshCw size={16} />
</button>
<Tooltip content="새로고침">
<button
onClick={fetchLogFiles}
className="p-2 text-zinc-400 hover:text-mc-green hover:bg-zinc-800 rounded-lg transition-all hover:rotate-180 duration-300"
>
<RefreshCw size={16} />
</button>
</Tooltip>
</div>
{/* 필터 드롭다운 */}
@ -1416,35 +1418,37 @@ export default function Admin({ isMobile = false }) {
</p>
</div>
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
<button
onClick={(e) => deleteLogFile(file, e)}
className="p-2 text-zinc-400 hover:text-red-500 hover:bg-zinc-700 rounded-lg transition-colors"
title="삭제"
>
<Trash2 size={16} />
</button>
<button
onClick={async (e) => {
e.stopPropagation();
const token = localStorage.getItem('token');
const response = await fetch(`/api/admin/logfile?id=${file.id}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
const blob = await response.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = file.fileName;
a.click();
URL.revokeObjectURL(url);
}
}}
className="p-2 text-zinc-400 hover:text-white hover:bg-zinc-700 rounded-lg transition-colors"
title="다운로드"
>
<Download size={16} />
</button>
<Tooltip content="삭제">
<button
onClick={(e) => deleteLogFile(file, e)}
className="p-2 text-zinc-400 hover:text-red-500 hover:bg-zinc-700 rounded-lg transition-colors"
>
<Trash2 size={16} />
</button>
</Tooltip>
<Tooltip content="다운로드">
<button
onClick={async (e) => {
e.stopPropagation();
const token = localStorage.getItem('token');
const response = await fetch(`/api/admin/logfile?id=${file.id}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
const blob = await response.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = file.fileName;
a.click();
URL.revokeObjectURL(url);
}
}}
className="p-2 text-zinc-400 hover:text-white hover:bg-zinc-700 rounded-lg transition-colors"
>
<Download size={16} />
</button>
</Tooltip>
</div>
</div>
))
@ -1540,13 +1544,14 @@ export default function Admin({ isMobile = false }) {
key={player.uuid}
className="bg-zinc-800/50 rounded-xl p-3 text-center group hover:bg-zinc-800 transition-colors relative"
>
<button
onClick={() => setWhitelistRemoveTarget(player)}
className="absolute top-2 right-2 p-1 text-zinc-500 hover:text-red-400 opacity-0 group-hover:opacity-100 transition-all"
title="제거"
>
<X size={14} />
</button>
<Tooltip content="제거">
<button
onClick={() => setWhitelistRemoveTarget(player)}
className="absolute top-2 right-2 p-1 text-zinc-500 hover:text-red-400 opacity-0 group-hover:opacity-100 transition-all"
>
<X size={14} />
</button>
</Tooltip>
<CachedSkin
uuid={player.uuid}
name={player.name}
@ -1599,58 +1604,62 @@ export default function Admin({ isMobile = false }) {
{/* 액션 버튼 - mt-2 추가 */}
<div className="flex justify-center gap-1 mt-2">
<button
onClick={() => {
setSelectedPlayer(player);
setDialogAction('op');
setShowPlayerDialog(true);
}}
className={`p-2 rounded-lg transition-colors ${
player.isOp
? 'bg-yellow-500/20 text-yellow-500 hover:bg-yellow-500/30'
: 'bg-zinc-800 text-zinc-400 hover:text-yellow-500'
}`}
title="OP"
>
<Crown size={16} />
</button>
{player.isOnline && (
<Tooltip content="OP">
<button
onClick={() => {
setSelectedPlayer(player);
setDialogAction('kick');
setDialogAction('op');
setShowPlayerDialog(true);
}}
className="p-2 bg-zinc-800 text-zinc-400 hover:text-orange-500 rounded-lg transition-colors"
title="킥"
className={`p-2 rounded-lg transition-colors ${
player.isOp
? 'bg-yellow-500/20 text-yellow-500 hover:bg-yellow-500/30'
: 'bg-zinc-800 text-zinc-400 hover:text-yellow-500'
}`}
>
<UserX size={16} />
<Crown size={16} />
</button>
</Tooltip>
{player.isOnline && (
<Tooltip content="킥">
<button
onClick={() => {
setSelectedPlayer(player);
setDialogAction('kick');
setShowPlayerDialog(true);
}}
className="p-2 bg-zinc-800 text-zinc-400 hover:text-orange-500 rounded-lg transition-colors"
>
<UserX size={16} />
</button>
</Tooltip>
)}
{player.isBanned ? (
<button
onClick={() => {
setSelectedPlayer(player);
setDialogAction('unban');
setShowPlayerDialog(true);
}}
className="p-2 bg-green-500/20 text-green-500 hover:bg-green-500/30 rounded-lg transition-colors"
title="차단 해제"
>
<Check size={16} />
</button>
<Tooltip content="차단 해제">
<button
onClick={() => {
setSelectedPlayer(player);
setDialogAction('unban');
setShowPlayerDialog(true);
}}
className="p-2 bg-green-500/20 text-green-500 hover:bg-green-500/30 rounded-lg transition-colors"
>
<Check size={16} />
</button>
</Tooltip>
) : (
<button
onClick={() => {
setSelectedPlayer(player);
setDialogAction('ban');
setShowPlayerDialog(true);
}}
className="p-2 bg-zinc-800 text-zinc-400 hover:text-red-500 rounded-lg transition-colors"
title="밴"
>
<Ban size={16} />
</button>
<Tooltip content="밴">
<button
onClick={() => {
setSelectedPlayer(player);
setDialogAction('ban');
setShowPlayerDialog(true);
}}
className="p-2 bg-zinc-800 text-zinc-400 hover:text-red-500 rounded-lg transition-colors"
>
<Ban size={16} />
</button>
</Tooltip>
)}
</div>
</div>
@ -2098,25 +2107,27 @@ export default function Admin({ isMobile = false }) {
</div>
</div>
<div className="flex items-center gap-1">
<button
onClick={() => {
setModpackDialogMode('edit');
setEditingModpack(pack);
setModpackForm({ version: pack.version, changelog: pack.changelog || '' });
setShowModpackDialog(true);
}}
className="p-2 text-zinc-400 hover:text-blue-400 transition-colors"
title="수정"
>
<Pencil size={16} />
</button>
<button
onClick={() => setModpackDeleteTarget(pack)}
className="p-2 text-zinc-400 hover:text-red-400 transition-colors"
title="삭제"
>
<Trash2 size={16} />
</button>
<Tooltip content="수정">
<button
onClick={() => {
setModpackDialogMode('edit');
setEditingModpack(pack);
setModpackForm({ version: pack.version, changelog: pack.changelog || '' });
setShowModpackDialog(true);
}}
className="p-2 text-zinc-400 hover:text-blue-400 transition-colors"
>
<Pencil size={16} />
</button>
</Tooltip>
<Tooltip content="삭제">
<button
onClick={() => setModpackDeleteTarget(pack)}
className="p-2 text-zinc-400 hover:text-red-400 transition-colors"
>
<Trash2 size={16} />
</button>
</Tooltip>
</div>
</div>
)

View file

@ -4,6 +4,7 @@ import { Activity, Skull, Heart, LocateFixed, Box, Sword, Clock, Calendar, Refre
import { motion } from 'framer-motion';
import { io } from 'socket.io-client';
import { formatDate, formatPlayTimeMs } from '../utils/formatters';
import Tooltip from '../components/Tooltip';
// 3D ( / )
@ -405,9 +406,11 @@ const ItemStatRow = ({ item, translate, icons }) => {
</div>
)}
<div className="flex-1 min-w-0">
<p className="text-white text-sm font-medium truncate" title={translate(item.id)}>
{translate(item.id)}
</p>
<Tooltip content={translate(item.id)}>
<p className="text-white text-sm font-medium truncate">
{translate(item.id)}
</p>
</Tooltip>
<div className="flex flex-wrap gap-x-2 text-xs text-gray-400">
{item.mined > 0 && <span>채굴 <span className="text-yellow-400">{item.mined}</span></span>}
{item.used > 0 && <span>사용 <span className="text-blue-400">{item.used}</span></span>}
@ -458,9 +461,11 @@ const MobStatRow = ({ mob, translate, icons }) => {
</div>
)}
<div className="flex-1 min-w-0">
<p className="text-white text-sm font-medium truncate" title={translate(mob.id)}>
{translate(mob.id)}
</p>
<Tooltip content={translate(mob.id)}>
<p className="text-white text-sm font-medium truncate">
{translate(mob.id)}
</p>
</Tooltip>
<div className="flex flex-wrap gap-x-2 text-xs text-gray-400">
{mob.killed > 0 && <span>처치 <span className="text-red-400">{mob.killed}</span></span>}
{mob.killedBy > 0 && <span>죽음 <span className="text-gray-300">{mob.killedBy}</span></span>}

View file

@ -433,7 +433,9 @@ const ServerDetail = ({ isMobile = false }) => {
<ul className="grid grid-cols-1 md:grid-cols-2 gap-2">
{server.mods.map((mod, index) => (
<li key={index} className="text-sm flex items-center justify-between p-3 bg-black/20 rounded-lg hover:bg-black/30 transition-colors">
<span className="truncate font-medium text-gray-300" title={mod.id}>{mod.id}</span>
<Tooltip content={mod.id}>
<span className="truncate font-medium text-gray-300">{mod.id}</span>
</Tooltip>
<span className="text-xs text-gray-500 font-mono ml-2 px-2 py-0.5 bg-white/5 rounded">{mod.version}</span>
</li>
))}
@ -457,7 +459,9 @@ const ServerDetail = ({ isMobile = false }) => {
<ul className="grid grid-cols-1 md:grid-cols-2 gap-2">
{server.mods.map((mod, index) => (
<li key={index} className="text-sm flex items-center justify-between p-3 bg-black/20 rounded-lg hover:bg-black/30 transition-colors">
<span className="truncate font-medium text-gray-300" title={mod.id}>{mod.id}</span>
<Tooltip content={mod.id}>
<span className="truncate font-medium text-gray-300">{mod.id}</span>
</Tooltip>
<span className="text-xs text-gray-500 font-mono ml-2 px-2 py-0.5 bg-white/5 rounded">{mod.version}</span>
</li>
))}