refactor: 모든 페이지의 title 속성을 Tooltip 컴포넌트로 통일
- Admin.jsx: 버튼들의 title을 Tooltip으로 변경 (새로고침, 삭제, 다운로드, OP, 킥, 밴 등) - PlayerStatsPage.jsx: 아이템/몹 이름에 Tooltip 적용 - ServerDetail.jsx: 적용된 모드 목록에 Tooltip 적용
This commit is contained in:
parent
01aa85f041
commit
3661de82ca
3 changed files with 142 additions and 122 deletions
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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>}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue