feat: Complete code integration of modules (#18)

The complete code integration is done.

Co-authored-by: Sagnik <sagnik7896@gmail.com>
Reviewed-on: #18
This commit was merged in pull request #18.
This commit is contained in:
2026-04-12 19:20:14 +05:30
parent 248d92042f
commit 4645ff737b
27 changed files with 3393 additions and 50 deletions

View File

@@ -16,6 +16,7 @@ import { useMarketingStore } from '@/store/useMarketingStore';
import { useCurrency } from '@/store/useCurrencyStore';
import type { Campaign, MarketingAsset, LiveOptimizationEvent, LiveEventType } from '@/types';
import { GroundTruthPicker } from './GroundTruthPicker';
import { CatalystMarketingTab } from './CatalystMarketingTab';
import type { GroundTruthSelection } from './GroundTruthPicker';
// ── Design tokens ─────────────────────────────────────────────────────────────
@@ -936,13 +937,14 @@ function WarRoom() {
// Tab Bar
// ─────────────────────────────────────────────────────────────────────────────
type TabId = 'studio' | 'command' | 'intelligence' | 'war-room';
type TabId = 'studio' | 'command' | 'intelligence' | 'war-room' | 'marketing';
const TABS: Array<{ id: TabId; label: string; icon: LucideIcon }> = [
{ id: 'studio', label: 'The Studio', icon: Clapperboard },
{ id: 'command', label: 'Campaign Command', icon: Megaphone },
{ id: 'intelligence', label: 'Intelligence & ROI', icon: BarChart3 },
{ id: 'war-room', label: 'War Room', icon: Globe },
{ id: 'marketing', label: 'Marketing', icon: TrendingUp },
];
// ─────────────────────────────────────────────────────────────────────────────
@@ -957,6 +959,7 @@ export function Catalyst() {
'command': <CampaignCommand />,
'intelligence': <IntelligenceROI />,
'war-room': <WarRoom />,
'marketing': <CatalystMarketingTab />,
};
return (

View File

@@ -0,0 +1,263 @@
import { useEffect, useMemo, useState } from 'react';
import { Activity, BarChart3, DatabaseZap, Megaphone, RefreshCw, Sparkles } from 'lucide-react';
import {
getCatalystCampaigns,
getLeadDemographics,
getSentimentScatter,
seedSyntheticLeads,
type LeadDemographics,
type MarketingCampaignSummary,
type ScatterDataPoint,
} from '@/lib/api';
function formatMoney(value: number) {
return new Intl.NumberFormat('en-AE', {
style: 'currency',
currency: 'AED',
maximumFractionDigits: 0,
}).format(value);
}
function SectionCard({
title,
icon: Icon,
children,
subtitle,
}: {
title: string;
icon: typeof Activity;
subtitle?: string;
children: React.ReactNode;
}) {
return (
<section className="rounded-2xl border border-white/10 bg-[rgba(8,10,18,0.82)] p-5">
<div className="flex items-center gap-3 mb-4">
<div className="flex h-9 w-9 items-center justify-center rounded-xl border border-blue-400/20 bg-blue-500/10">
<Icon className="h-4 w-4 text-blue-300" />
</div>
<div>
<h3 className="text-sm font-semibold text-white">{title}</h3>
{subtitle && <p className="text-xs text-white/50 mt-0.5">{subtitle}</p>}
</div>
</div>
{children}
</section>
);
}
function SummaryMetric({ label, value }: { label: string; value: string | number }) {
return (
<div className="rounded-xl border border-white/8 bg-white/5 p-4">
<div className="text-[11px] uppercase tracking-[0.18em] text-white/45">{label}</div>
<div className="mt-2 text-2xl font-semibold text-white">{value}</div>
</div>
);
}
export function CatalystMarketingTab() {
const [campaigns, setCampaigns] = useState<MarketingCampaignSummary[]>([]);
const [scatter, setScatter] = useState<ScatterDataPoint[]>([]);
const [demographics, setDemographics] = useState<LeadDemographics | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [seeding, setSeeding] = useState(false);
useEffect(() => {
let active = true;
const load = async () => {
try {
const [campaignRows, scatterRows, demographicRows] = await Promise.all([
getCatalystCampaigns(),
getSentimentScatter(),
getLeadDemographics(),
]);
if (!active) return;
setCampaigns(campaignRows);
setScatter(scatterRows);
setDemographics(demographicRows);
setError(null);
} catch (err) {
if (!active) return;
setError(err instanceof Error ? err.message : 'Failed to load marketing intelligence');
} finally {
if (active) setLoading(false);
}
};
void load();
return () => {
active = false;
};
}, []);
const totals = useMemo(() => {
const totalBudget = campaigns.reduce((sum, campaign) => sum + campaign.budget, 0);
const totalSpent = campaigns.reduce((sum, campaign) => sum + campaign.spent, 0);
const totalLeads = scatter.length;
const whales = scatter.filter((item) => item.qualification === 'WHALE').length;
const avgSentiment = totalLeads
? Math.round(scatter.reduce((sum, item) => sum + item.sentiment_score, 0) / totalLeads)
: 0;
return { totalBudget, totalSpent, totalLeads, whales, avgSentiment };
}, [campaigns, scatter]);
const handleSeed = async () => {
setSeeding(true);
try {
await seedSyntheticLeads(100);
const [scatterRows, demographicRows] = await Promise.all([
getSentimentScatter(),
getLeadDemographics(),
]);
setScatter(scatterRows);
setDemographics(demographicRows);
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'Synthetic seed failed');
} finally {
setSeeding(false);
}
};
return (
<div className="space-y-4">
<SectionCard
title="Integrated Marketing Surface"
subtitle="Sourik-style campaign intelligence stacked inside the root Catalyst shell."
icon={Sparkles}
>
<div className="grid grid-cols-1 gap-3 md:grid-cols-5">
<SummaryMetric label="Campaigns" value={campaigns.length} />
<SummaryMetric label="Tracked Leads" value={totals.totalLeads} />
<SummaryMetric label="Whales" value={totals.whales} />
<SummaryMetric label="Avg Sentiment" value={totals.avgSentiment} />
<SummaryMetric label="Active Spend" value={formatMoney(totals.totalSpent)} />
</div>
</SectionCard>
<SectionCard
title="Campaign Manager"
subtitle="Unified Meta-first campaign strip, vertically ported into the Marketing sub-tab."
icon={Megaphone}
>
{loading ? (
<p className="text-sm text-white/50">Loading campaign intelligence</p>
) : (
<div className="space-y-3">
{campaigns.map((campaign) => (
<div key={campaign.id} className="rounded-xl border border-white/8 bg-white/5 p-4">
<div className="flex items-start justify-between gap-4">
<div>
<div className="text-sm font-medium text-white">{campaign.name}</div>
<div className="mt-1 text-[11px] uppercase tracking-wide text-white/45">
{campaign.platform} · {campaign.status}
</div>
</div>
<div className="text-right text-xs text-white/65">
<div>Budget {formatMoney(campaign.budget)}</div>
<div>Spent {formatMoney(campaign.spent)}</div>
</div>
</div>
<div className="mt-3 grid grid-cols-2 gap-3 text-xs text-white/65 md:grid-cols-4">
<div>Impressions {campaign.impressions.toLocaleString()}</div>
<div>Clicks {campaign.clicks.toLocaleString()}</div>
<div>Conversions {campaign.conversions.toLocaleString()}</div>
<div>
CTR {campaign.impressions ? ((campaign.clicks / campaign.impressions) * 100).toFixed(2) : '0.00'}%
</div>
</div>
</div>
))}
</div>
)}
</SectionCard>
<SectionCard
title="Lead Intelligence Feed"
subtitle="Live analytics from the root CRM routes."
icon={BarChart3}
>
<div className="grid grid-cols-1 gap-4 xl:grid-cols-[1.35fr_0.65fr]">
<div className="space-y-2">
{scatter.slice(0, 14).map((lead) => (
<div key={lead.id} className="rounded-xl border border-white/8 bg-white/5 p-3">
<div className="flex items-center justify-between gap-3">
<div>
<div className="text-sm font-medium text-white">{lead.name}</div>
<div className="text-xs text-white/50">
{lead.qualification} · {lead.kanban_status}
</div>
</div>
<div className="text-right text-xs text-white/65">
<div>Score {lead.score}</div>
<div>Sentiment {lead.sentiment_score}</div>
</div>
</div>
</div>
))}
{!loading && scatter.length === 0 && <p className="text-sm text-white/50">No lead analytics available yet.</p>}
</div>
<div className="space-y-3">
<div className="rounded-xl border border-white/8 bg-white/5 p-4">
<div className="text-xs uppercase tracking-[0.18em] text-white/45">Lead Sources</div>
<div className="mt-3 space-y-2">
{(demographics?.by_source ?? []).map((row) => (
<div key={row.source} className="flex items-center justify-between text-sm text-white/80">
<span>{row.source}</span>
<span>{row.lead_count}</span>
</div>
))}
</div>
</div>
<div className="rounded-xl border border-white/8 bg-white/5 p-4">
<div className="text-xs uppercase tracking-[0.18em] text-white/45">Qualification Mix</div>
<div className="mt-3 space-y-2">
{(demographics?.by_qualification ?? []).map((row) => (
<div key={row.qualification} className="flex items-center justify-between text-sm text-white/80">
<span>{row.qualification}</span>
<span>{row.lead_count}</span>
</div>
))}
</div>
</div>
</div>
</div>
</SectionCard>
<SectionCard
title="Platform Status and Verification"
subtitle="Production-readiness controls kept inside the same vertical marketing surface."
icon={DatabaseZap}
>
<div className="grid grid-cols-1 gap-4 xl:grid-cols-[1fr_auto]">
<div className="grid grid-cols-1 gap-3 md:grid-cols-3">
<div className="rounded-xl border border-white/8 bg-white/5 p-4 text-sm text-white/75">
<div className="mb-1 text-white font-medium">CRM Analytics</div>
<div>{totals.totalLeads > 0 ? 'Live data available' : 'No seeded verification data yet'}</div>
</div>
<div className="rounded-xl border border-white/8 bg-white/5 p-4 text-sm text-white/75">
<div className="mb-1 text-white font-medium">Catalyst Contracts</div>
<div>{campaigns.length > 0 ? 'Marketing tab wired to root endpoints' : 'Campaign summary unavailable'}</div>
</div>
<div className="rounded-xl border border-white/8 bg-white/5 p-4 text-sm text-white/75">
<div className="mb-1 text-white font-medium">Spend Capacity</div>
<div>Total budget {formatMoney(totals.totalBudget)}</div>
</div>
</div>
<button
type="button"
onClick={() => void handleSeed()}
disabled={seeding}
className="inline-flex items-center justify-center gap-2 rounded-xl border border-blue-400/25 bg-blue-500/10 px-4 py-3 text-sm font-medium text-blue-200 disabled:opacity-50"
>
{seeding ? <RefreshCw className="h-4 w-4 animate-spin" /> : <DatabaseZap className="h-4 w-4" />}
Seed 100 Synthetic Leads
</button>
</div>
{error && <p className="mt-4 text-sm text-red-300">{error}</p>}
</SectionCard>
</div>
);
}