diff --git a/src/components/admin/campaign-roi-cards.tsx b/src/components/admin/campaign-roi-cards.tsx new file mode 100644 index 0000000..c3f4dbd --- /dev/null +++ b/src/components/admin/campaign-roi-cards.tsx @@ -0,0 +1,93 @@ +import { useMemo } from 'react'; +import { Link } from 'react-router'; + +import { formatCurrency } from '@/lib/format'; +import { cx } from '@/utils/cx'; +import type { Campaign } from '@/types/entities'; + +interface CampaignRoiCardsProps { + campaigns: Campaign[]; +} + +type CampaignWithCac = Campaign & { + cac: number; + conversionRate: number; + budgetProgress: number; +}; + +export const CampaignRoiCards = ({ campaigns }: CampaignRoiCardsProps) => { + const sorted = useMemo((): CampaignWithCac[] => { + return campaigns + .map((campaign) => { + const spent = campaign.amountSpent?.amountMicros ?? 0; + const converted = campaign.convertedCount ?? 0; + const leadCount = campaign.leadCount ?? 0; + const budgetMicros = campaign.budget?.amountMicros ?? 1; + + const cac = converted > 0 ? spent / converted : Infinity; + const conversionRate = leadCount > 0 ? converted / leadCount : 0; + const budgetProgress = budgetMicros > 0 ? spent / budgetMicros : 0; + + return { ...campaign, cac, conversionRate, budgetProgress }; + }) + .sort((a, b) => a.cac - b.cac); + }, [campaigns]); + + const getHealthColor = (rate: number): string => { + if (rate >= 0.1) return 'bg-success-500'; + if (rate >= 0.05) return 'bg-warning-500'; + return 'bg-error-500'; + }; + + return ( +
+ {source.leadsReceivedLast24h ?? 0} leads in 24h +
+ + {source.lastSuccessfulSyncAt && ( ++ Last sync: {formatRelativeTime(source.lastSuccessfulSyncAt)} +
+ )} + + {showAuthBadge && ( ++ {source.lastErrorMessage} +
+ )} +Coming soon — team performance metrics and management tools.
+