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>
|
||||
{!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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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>}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
))}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue