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 (
|
||||
<motion.div
|
||||
initial={isInitialLoad ? { opacity: 0, scale: 0.95 } : false}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={isInitialLoad ? { delay: index * 0.05 } : { duration: 0.15 }}
|
||||
initial={isInitialLoad ? { opacity: 0, y: 10 } : false}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={isInitialLoad ? { delay: index * 0.05, duration: 0.2 } : { duration: 0.15 }}
|
||||
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="flex items-center gap-3">
|
||||
<div
|
||||
className={`w-10 h-10 rounded-lg flex items-center justify-center ${
|
||||
bot.type === 'x'
|
||||
? 'bg-black'
|
||||
: 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)}에 업데이트됨`
|
||||
: '아직 업데이트 없음'}
|
||||
{/* 상단: 이름 + 상태 */}
|
||||
<div className="p-4">
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-semibold text-gray-900 truncate">{bot.name}</h3>
|
||||
<p className="text-xs text-gray-400 mt-0.5">
|
||||
{bot.last_check_at ? formatTime(bot.last_check_at) : '대기 중'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
className={`flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-full ${statusInfo.bg} ${statusInfo.color}`}
|
||||
>
|
||||
<span
|
||||
className={`w-1.5 h-1.5 rounded-full ${statusInfo.dot} ${bot.status === 'running' ? 'animate-pulse' : ''}`}
|
||||
></span>
|
||||
{statusInfo.text}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 통계 정보 */}
|
||||
<div className="grid grid-cols-3 divide-x divide-gray-100 bg-gray-50/50">
|
||||
<div className="p-3 text-center">
|
||||
<div className="text-lg font-bold text-gray-900">{bot.schedules_added || 0}</div>
|
||||
<div className="text-xs text-gray-400">총 추가</div>
|
||||
</div>
|
||||
<div className="p-3 text-center">
|
||||
<div
|
||||
className={`text-lg font-bold ${bot.last_added_count > 0 ? 'text-green-500' : 'text-gray-400'}`}
|
||||
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}`}
|
||||
>
|
||||
+{bot.last_added_count || 0}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400">마지막</div>
|
||||
<span
|
||||
className={`w-1.5 h-1.5 rounded-full ${statusInfo.dot} ${bot.status === 'running' ? 'animate-pulse' : ''}`}
|
||||
/>
|
||||
{statusInfo.text}
|
||||
</span>
|
||||
</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 className="flex items-center gap-4 text-sm">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-gray-400">추가</span>
|
||||
<span className="font-semibold text-gray-900">{bot.schedules_added || 0}</span>
|
||||
</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 ${bot.last_added_count > 0 ? 'text-green-600' : 'text-gray-400'}`}>
|
||||
+{bot.last_added_count || 0}
|
||||
</span>
|
||||
</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>
|
||||
|
||||
|
|
@ -168,46 +149,44 @@ const BotCard = memo(function BotCard({
|
|||
)}
|
||||
|
||||
{/* 액션 버튼 */}
|
||||
<div className="p-4 border-t border-gray-100">
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => onSync(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"
|
||||
>
|
||||
{syncing === bot.id ? (
|
||||
<>
|
||||
<RefreshCw size={16} className="animate-spin" />
|
||||
<span>동기화 중...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download size={16} />
|
||||
<span>전체 동기화</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
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 ${
|
||||
bot.status === 'running'
|
||||
? 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
||||
: 'bg-green-500 text-white hover:bg-green-600'
|
||||
}`}
|
||||
>
|
||||
{bot.status === 'running' ? (
|
||||
<>
|
||||
<Square size={16} />
|
||||
<span>정지</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Play size={16} />
|
||||
<span>시작</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex border-t border-gray-100">
|
||||
<button
|
||||
onClick={() => onSync(bot.id)}
|
||||
disabled={syncing === bot.id}
|
||||
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 ? (
|
||||
<>
|
||||
<RefreshCw size={14} className="animate-spin" />
|
||||
<span>동기화 중</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download size={14} />
|
||||
<span>전체 동기화</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onToggle(bot.id, bot.status, bot.name)}
|
||||
className={`flex items-center justify-center gap-1.5 px-4 py-2.5 text-sm font-medium transition-colors ${
|
||||
bot.status === 'running'
|
||||
? 'text-gray-600 hover:bg-gray-50'
|
||||
: 'text-green-600 hover:bg-green-50'
|
||||
}`}
|
||||
>
|
||||
{bot.status === 'running' ? (
|
||||
<>
|
||||
<Square size={14} />
|
||||
<span>정지</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Play size={14} />
|
||||
<span>시작</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue