feat(agents): add search/filter, "New" badge, and duplicate name resolution
- Add search bar with real-time filtering on agent name and description - Add type filter chips (All, Veilleur, Chercheur, Surveillant, Personnalisé) - Add "New" badge on agents created within the last 24h (hydration-safe) - Auto-increment template names on duplicate install (e.g. "Veille Tech 2") - Add i18n keys for new UI elements in both fr and en locales Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
* Displays a single agent with status, actions, and metadata.
|
||||
*/
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { formatDistanceToNow } from 'date-fns'
|
||||
import { fr } from 'date-fns/locale/fr'
|
||||
import { enUS } from 'date-fns/locale/en-US'
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
} from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
import { getNotebookIcon } from '@/lib/notebook-icon'
|
||||
|
||||
// --- Types ---
|
||||
|
||||
@@ -37,6 +38,7 @@ interface AgentCardProps {
|
||||
isEnabled: boolean
|
||||
frequency: string
|
||||
lastRun: string | Date | null
|
||||
createdAt: string | Date
|
||||
updatedAt: string | Date
|
||||
_count: { actions: number }
|
||||
actions: { id: string; status: string; createdAt: string | Date }[]
|
||||
@@ -78,11 +80,16 @@ export function AgentCard({ agent, onEdit, onRefresh, onToggle }: AgentCardProps
|
||||
const [isRunning, setIsRunning] = useState(false)
|
||||
const [isDeleting, setIsDeleting] = useState(false)
|
||||
const [isToggling, setIsToggling] = useState(false)
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
// Prevent hydration mismatch for date formatting
|
||||
useEffect(() => { setMounted(true) }, [])
|
||||
|
||||
const config = typeConfig[agent.type || 'scraper'] || typeConfig.custom
|
||||
const Icon = config.icon
|
||||
const lastAction = agent.actions[0]
|
||||
const dateLocale = language === 'fr' ? fr : enUS
|
||||
const isNew = Date.now() - new Date(agent.createdAt).getTime() < 24 * 60 * 60 * 1000
|
||||
|
||||
const handleRun = async () => {
|
||||
setIsRunning(true)
|
||||
@@ -147,7 +154,14 @@ export function AgentCard({ agent, onEdit, onRefresh, onToggle }: AgentCardProps
|
||||
<Icon className={`w-4 h-4 ${config.color}`} />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<h3 className="font-semibold text-slate-800 truncate">{agent.name}</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="font-semibold text-slate-800 truncate">{agent.name}</h3>
|
||||
{mounted && isNew && (
|
||||
<span className="flex-shrink-0 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wider bg-emerald-100 text-emerald-700 rounded">
|
||||
{t('agents.newBadge')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span className={`text-xs font-medium ${config.color}`}>
|
||||
{t(`agents.types.${agent.type || 'custom'}`)}
|
||||
</span>
|
||||
@@ -173,7 +187,10 @@ export function AgentCard({ agent, onEdit, onRefresh, onToggle }: AgentCardProps
|
||||
</span>
|
||||
{agent.notebook && (
|
||||
<span className="flex items-center gap-1">
|
||||
{agent.notebook.icon || '📁'} {agent.notebook.name}
|
||||
{(() => {
|
||||
const Icon = getNotebookIcon(agent.notebook.icon)
|
||||
return <Icon className="w-3 h-3" />
|
||||
})()} {agent.notebook.name}
|
||||
</span>
|
||||
)}
|
||||
<span>{t('agents.metadata.executions', { count: agent._count.actions })}</span>
|
||||
@@ -191,7 +208,9 @@ export function AgentCard({ agent, onEdit, onRefresh, onToggle }: AgentCardProps
|
||||
{lastAction.status === 'running' && <Loader2 className="w-3 h-3 animate-spin" />}
|
||||
{t(statusKeys[lastAction.status] || lastAction.status)}
|
||||
{' - '}
|
||||
{formatDistanceToNow(new Date(lastAction.createdAt), { addSuffix: true, locale: dateLocale })}
|
||||
{mounted
|
||||
? formatDistanceToNow(new Date(lastAction.createdAt), { addSuffix: true, locale: dateLocale })
|
||||
: new Date(lastAction.createdAt).toISOString().split('T')[0]}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user