refactor: migrate loyalty-agent and loyalty-mcp-server components to a new modular structure with updated specs and frontend integration.
This commit is contained in:
109
src/components/campaign/CampaignList.tsx
Normal file
109
src/components/campaign/CampaignList.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface Campaign {
|
||||
id: string;
|
||||
name: string;
|
||||
status: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}
|
||||
|
||||
export function CampaignList() {
|
||||
const [campaigns, setCampaigns] = useState<Campaign[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedCampaignId, setSelectedCampaignId] = useState<string | null>(null);
|
||||
const [rules, setRules] = useState<any[]>([]);
|
||||
const [loadingRules, setLoadingRules] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('http://localhost:9332/api/v1/agent/campaigns')
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
if (Array.isArray(data)) setCampaigns(data);
|
||||
else if (data && Array.isArray(data.content)) setCampaigns(data.content);
|
||||
else if (data && Array.isArray(data.data)) setCampaigns(data.data);
|
||||
else setCampaigns([]);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to fetch campaigns', err);
|
||||
setCampaigns([]);
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
const handleSelectCampaign = (id: string) => {
|
||||
if (selectedCampaignId === id) {
|
||||
setSelectedCampaignId(null);
|
||||
setRules([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedCampaignId(id);
|
||||
setLoadingRules(true);
|
||||
fetch(`http://localhost:9332/api/v1/agent/campaigns/${id}/rules`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
if (Array.isArray(data)) setRules(data);
|
||||
else if (data && Array.isArray(data.content)) setRules(data.content);
|
||||
else if (data && Array.isArray(data.data)) setRules(data.data);
|
||||
else setRules([]);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to fetch rules', err);
|
||||
setRules([]);
|
||||
})
|
||||
.finally(() => setLoadingRules(false));
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <div className="p-4 text-center text-sm text-muted-foreground">Loading campaigns...</div>;
|
||||
}
|
||||
|
||||
if (campaigns.length === 0) {
|
||||
return <div className="p-4 text-center text-sm text-muted-foreground">No campaigns found.</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-3 custom-scrollbar">
|
||||
{campaigns.map((camp) => (
|
||||
<div key={camp.id} className="space-y-2">
|
||||
<div
|
||||
onClick={() => handleSelectCampaign(camp.id)}
|
||||
className={`glass-card rounded-xl p-4 text-sm group cursor-pointer relative overflow-hidden transition-all duration-200 ${selectedCampaignId === camp.id ? 'ring-2 ring-primary/50' : ''}`}>
|
||||
<div className="absolute inset-0 bg-primary/5 opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
<div className="relative z-10">
|
||||
<div className="font-semibold text-foreground text-base mb-1 group-hover:text-primary transition-colors">{camp.name || 'Unnamed Campaign'}</div>
|
||||
<div className="flex justify-between items-center text-xs text-muted-foreground mt-3">
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className={`w-2 h-2 rounded-full ${camp.status === 'A' ? 'bg-emerald-500 shadow-[0_0_8px_rgba(16,185,129,0.5)]' : 'bg-rose-500'}`} />
|
||||
{camp.status === 'A' ? 'Active' : camp.status}
|
||||
</span>
|
||||
<code className="bg-background/50 px-1.5 py-0.5 rounded text-[10px] uppercase">{camp.id}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedCampaignId === camp.id && (
|
||||
<div className="pl-4 pr-2 space-y-2 border-l-2 border-primary/20 ml-2 animate-in slide-in-from-top-2">
|
||||
{loadingRules ? (
|
||||
<div className="text-xs text-muted-foreground italic">Loading rules...</div>
|
||||
) : rules.length === 0 ? (
|
||||
<div className="text-xs text-muted-foreground italic">No rules found.</div>
|
||||
) : (
|
||||
rules.map((rule, idx) => (
|
||||
<div key={rule.id || idx} className="bg-background/40 rounded-lg p-3 text-xs border border-border/50">
|
||||
<div className="font-medium text-foreground mb-1">{rule.name || 'Unnamed Rule'}</div>
|
||||
<div className="text-muted-foreground line-clamp-2">{rule.description || 'No description'}</div>
|
||||
{rule.status && (
|
||||
<div className="mt-2 text-[10px] uppercase tracking-wider text-muted-foreground/80">Status: {rule.status}</div>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user