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

View file

@ -4,6 +4,7 @@ import { Activity, Skull, Heart, LocateFixed, Box, Sword, Clock, Calendar, Refre
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { io } from 'socket.io-client'; import { io } from 'socket.io-client';
import { formatDate, formatPlayTimeMs } from '../utils/formatters'; import { formatDate, formatPlayTimeMs } from '../utils/formatters';
import Tooltip from '../components/Tooltip';
// 3D ( / ) // 3D ( / )
@ -405,9 +406,11 @@ const ItemStatRow = ({ item, translate, icons }) => {
</div> </div>
)} )}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-white text-sm font-medium truncate" title={translate(item.id)}> <Tooltip content={translate(item.id)}>
{translate(item.id)} <p className="text-white text-sm font-medium truncate">
</p> {translate(item.id)}
</p>
</Tooltip>
<div className="flex flex-wrap gap-x-2 text-xs text-gray-400"> <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.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>} {item.used > 0 && <span>사용 <span className="text-blue-400">{item.used}</span></span>}
@ -458,9 +461,11 @@ const MobStatRow = ({ mob, translate, icons }) => {
</div> </div>
)} )}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-white text-sm font-medium truncate" title={translate(mob.id)}> <Tooltip content={translate(mob.id)}>
{translate(mob.id)} <p className="text-white text-sm font-medium truncate">
</p> {translate(mob.id)}
</p>
</Tooltip>
<div className="flex flex-wrap gap-x-2 text-xs text-gray-400"> <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.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>} {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"> <ul className="grid grid-cols-1 md:grid-cols-2 gap-2">
{server.mods.map((mod, index) => ( {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"> <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> <span className="text-xs text-gray-500 font-mono ml-2 px-2 py-0.5 bg-white/5 rounded">{mod.version}</span>
</li> </li>
))} ))}
@ -457,7 +459,9 @@ const ServerDetail = ({ isMobile = false }) => {
<ul className="grid grid-cols-1 md:grid-cols-2 gap-2"> <ul className="grid grid-cols-1 md:grid-cols-2 gap-2">
{server.mods.map((mod, index) => ( {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"> <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> <span className="text-xs text-gray-500 font-mono ml-2 px-2 py-0.5 bg-white/5 rounded">{mod.version}</span>
</li> </li>
))} ))}