refactor: BotCard 디자인 개선
- 타입별 아이콘 제거 (섹션 헤더에서 표시) - 통계 정보 가로 배열로 컴팩트하게 변경 - 버튼을 하단에 플랫하게 배치 - 전체적으로 미니멀하고 깔끔한 스타일로 변경 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
68027f0654
commit
b3357e0663
1 changed files with 74 additions and 95 deletions
|
|
@ -95,68 +95,49 @@ const BotCard = memo(function BotCard({
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={isInitialLoad ? { opacity: 0, scale: 0.95 } : false}
|
initial={isInitialLoad ? { opacity: 0, y: 10 } : false}
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={isInitialLoad ? { delay: index * 0.05 } : { duration: 0.15 }}
|
transition={isInitialLoad ? { delay: index * 0.05, duration: 0.2 } : { duration: 0.15 }}
|
||||||
onAnimationComplete={onAnimationComplete}
|
onAnimationComplete={onAnimationComplete}
|
||||||
className="relative bg-gradient-to-br from-gray-50 to-white rounded-xl border border-gray-200 overflow-hidden hover:shadow-md transition-all"
|
className="group relative bg-white rounded-xl border border-gray-100 overflow-hidden hover:border-gray-200 hover:shadow-sm transition-all"
|
||||||
>
|
>
|
||||||
{/* 상단 헤더 */}
|
{/* 상단: 이름 + 상태 */}
|
||||||
<div className="flex items-center justify-between p-4 border-b border-gray-100">
|
<div className="p-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-start justify-between mb-3">
|
||||||
<div
|
<div className="flex-1 min-w-0">
|
||||||
className={`w-10 h-10 rounded-lg flex items-center justify-center ${
|
<h3 className="font-semibold text-gray-900 truncate">{bot.name}</h3>
|
||||||
bot.type === 'x'
|
<p className="text-xs text-gray-400 mt-0.5">
|
||||||
? 'bg-black'
|
{bot.last_check_at ? formatTime(bot.last_check_at) : '대기 중'}
|
||||||
: bot.type === 'meilisearch'
|
|
||||||
? 'bg-[#ddf1fd]'
|
|
||||||
: 'bg-red-50'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{bot.type === 'x' ? (
|
|
||||||
<XIcon size={20} fill="white" />
|
|
||||||
) : bot.type === 'meilisearch' ? (
|
|
||||||
<MeilisearchIcon size={20} />
|
|
||||||
) : (
|
|
||||||
<Youtube size={20} className="text-red-500" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-bold text-gray-900">{bot.name}</h3>
|
|
||||||
<p className="text-xs text-gray-400">
|
|
||||||
{bot.last_check_at
|
|
||||||
? `${formatTime(bot.last_check_at)}에 업데이트됨`
|
|
||||||
: '아직 업데이트 없음'}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<span
|
<span
|
||||||
className={`flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-full ${statusInfo.bg} ${statusInfo.color}`}
|
className={`flex-shrink-0 flex items-center gap-1.5 px-2 py-0.5 text-xs font-medium rounded-full ${statusInfo.bg} ${statusInfo.color}`}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`w-1.5 h-1.5 rounded-full ${statusInfo.dot} ${bot.status === 'running' ? 'animate-pulse' : ''}`}
|
className={`w-1.5 h-1.5 rounded-full ${statusInfo.dot} ${bot.status === 'running' ? 'animate-pulse' : ''}`}
|
||||||
></span>
|
/>
|
||||||
{statusInfo.text}
|
{statusInfo.text}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 통계 정보 */}
|
{/* 통계 */}
|
||||||
<div className="grid grid-cols-3 divide-x divide-gray-100 bg-gray-50/50">
|
<div className="flex items-center gap-4 text-sm">
|
||||||
<div className="p-3 text-center">
|
<div className="flex items-center gap-1.5">
|
||||||
<div className="text-lg font-bold text-gray-900">{bot.schedules_added || 0}</div>
|
<span className="text-gray-400">추가</span>
|
||||||
<div className="text-xs text-gray-400">총 추가</div>
|
<span className="font-semibold text-gray-900">{bot.schedules_added || 0}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3 text-center">
|
<div className="w-px h-3 bg-gray-200" />
|
||||||
<div
|
<div className="flex items-center gap-1.5">
|
||||||
className={`text-lg font-bold ${bot.last_added_count > 0 ? 'text-green-500' : 'text-gray-400'}`}
|
<span className="text-gray-400">최근</span>
|
||||||
>
|
<span className={`font-semibold ${bot.last_added_count > 0 ? 'text-green-600' : 'text-gray-400'}`}>
|
||||||
+{bot.last_added_count || 0}
|
+{bot.last_added_count || 0}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-400">마지막</div>
|
<div className="w-px h-3 bg-gray-200" />
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<span className="text-gray-400">간격</span>
|
||||||
|
<span className="font-semibold text-gray-900">{formatInterval(bot.check_interval)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3 text-center">
|
|
||||||
<div className="text-lg font-bold text-gray-900">{formatInterval(bot.check_interval)}</div>
|
|
||||||
<div className="text-xs text-gray-400">업데이트 간격</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -168,47 +149,45 @@ const BotCard = memo(function BotCard({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 액션 버튼 */}
|
{/* 액션 버튼 */}
|
||||||
<div className="p-4 border-t border-gray-100">
|
<div className="flex border-t border-gray-100">
|
||||||
<div className="flex gap-2">
|
|
||||||
<button
|
<button
|
||||||
onClick={() => onSync(bot.id)}
|
onClick={() => onSync(bot.id)}
|
||||||
disabled={syncing === bot.id}
|
disabled={syncing === bot.id}
|
||||||
className="flex-1 flex items-center justify-center gap-2 px-4 py-2.5 bg-blue-500 text-white rounded-lg font-medium transition-colors hover:bg-blue-600 disabled:opacity-50"
|
className="flex-1 flex items-center justify-center gap-1.5 px-4 py-2.5 text-sm font-medium text-gray-600 hover:bg-gray-50 transition-colors disabled:opacity-50 border-r border-gray-100"
|
||||||
>
|
>
|
||||||
{syncing === bot.id ? (
|
{syncing === bot.id ? (
|
||||||
<>
|
<>
|
||||||
<RefreshCw size={16} className="animate-spin" />
|
<RefreshCw size={14} className="animate-spin" />
|
||||||
<span>동기화 중...</span>
|
<span>동기화 중</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Download size={16} />
|
<Download size={14} />
|
||||||
<span>전체 동기화</span>
|
<span>전체 동기화</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => onToggle(bot.id, bot.status, bot.name)}
|
onClick={() => onToggle(bot.id, bot.status, bot.name)}
|
||||||
className={`flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg font-medium transition-colors ${
|
className={`flex items-center justify-center gap-1.5 px-4 py-2.5 text-sm font-medium transition-colors ${
|
||||||
bot.status === 'running'
|
bot.status === 'running'
|
||||||
? 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
? 'text-gray-600 hover:bg-gray-50'
|
||||||
: 'bg-green-500 text-white hover:bg-green-600'
|
: 'text-green-600 hover:bg-green-50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{bot.status === 'running' ? (
|
{bot.status === 'running' ? (
|
||||||
<>
|
<>
|
||||||
<Square size={16} />
|
<Square size={14} />
|
||||||
<span>정지</span>
|
<span>정지</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Play size={16} />
|
<Play size={14} />
|
||||||
<span>시작</span>
|
<span>시작</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue