209 lines
6.1 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { useMCPApp } from '../../hooks/useMCPApp';
import { useSmartAction } from '../../hooks/useSmartAction';
import { PageHeader } from '../../components/layout/PageHeader';
import { MetricCard } from '../../components/data/MetricCard';
import { DataTable, Column } from '../../components/data/DataTable';
import { StatusBadge } from '../../components/data/StatusBadge';
import { SelectField } from '../../components/forms/SelectField';
import { Button } from '../../components/shared/Button';
interface SubmissionRow {
id: string;
businessName: string;
status: string;
brandScore?: number;
submittedAt: string;
updatedAt: string;
}
export function App() {
const { app, isConnected, toolResult } = useMCPApp();
const { execute, isLoading } = useSmartAction(app);
const [stats, setStats] = useState({
total: 0,
pending: 0,
approved: 0,
failed: 0,
successRate: 0,
});
const [submissions, setSubmissions] = useState<SubmissionRow[]>([]);
const [statusFilter, setStatusFilter] = useState<string>('');
// Load data from tool result
useEffect(() => {
if (toolResult) {
if (toolResult.stats) {
setStats(toolResult.stats);
}
if (toolResult.submissions) {
setSubmissions(toolResult.submissions);
}
}
}, [toolResult]);
// Auto-refresh stats every 60 seconds
useEffect(() => {
const fetchStats = async () => {
try {
await execute('a2p_get_stats', {});
} catch (error) {
console.error('Failed to fetch stats:', error);
}
};
// Initial fetch
if (isConnected) {
fetchStats();
}
// Set up interval
const interval = setInterval(fetchStats, 60000);
return () => clearInterval(interval);
}, [isConnected, execute]);
const handleRefresh = async () => {
try {
await execute('a2p_get_stats', {});
} catch (error) {
console.error('Refresh failed:', error);
}
};
const handleRowClick = async (row: SubmissionRow) => {
// Tell model to open detail view
await app.sendMessage(`Please show details for submission ${row.id} (${row.businessName})`);
};
const filteredSubmissions = statusFilter
? submissions.filter((s) => s.status === statusFilter)
: submissions;
const columns: Column<SubmissionRow>[] = [
{
key: 'businessName',
label: 'Business Name',
sortable: true,
},
{
key: 'status',
label: 'Status',
sortable: true,
render: (value: string) => <StatusBadge status={value as any} size="sm" />,
},
{
key: 'brandScore',
label: 'Brand Score',
sortable: true,
render: (value: number | undefined) => (
<span
style={{
fontWeight: 600,
color:
value && value >= 75
? 'var(--color-success)'
: value && value >= 50
? 'var(--color-warning)'
: 'var(--color-text-secondary)',
}}
>
{value || '—'}
</span>
),
},
{
key: 'submittedAt',
label: 'Submitted',
sortable: true,
render: (value: string) => new Date(value).toLocaleDateString(),
},
{
key: 'updatedAt',
label: 'Last Updated',
sortable: true,
render: (value: string) => new Date(value).toLocaleString(),
},
];
if (!isConnected) {
return (
<div style={{ padding: 'var(--spacing-8)', textAlign: 'center' }}>
<p>Connecting to MCP host...</p>
</div>
);
}
return (
<div style={{ minHeight: '100vh', background: 'var(--color-background-primary)' }}>
<PageHeader
title="A2P AutoPilot Dashboard"
subtitle="Monitor your A2P registration submissions"
action={
<Button onClick={handleRefresh} loading={isLoading} variant="secondary">
{isLoading ? 'Refreshing...' : '↻ Refresh'}
</Button>
}
/>
<div className="container" style={{ padding: 'var(--spacing-6)' }}>
{/* Metrics Row */}
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))',
gap: 'var(--spacing-4)',
marginBottom: 'var(--spacing-6)',
}}
>
<MetricCard label="Total Submissions" value={stats.total} color="blue" />
<MetricCard label="Pending" value={stats.pending} color="yellow" />
<MetricCard label="Approved" value={stats.approved} color="green" />
<MetricCard label="Failed" value={stats.failed} color="red" />
<MetricCard label="Success Rate" value={`${stats.successRate}%`} color="purple" />
</div>
{/* Filters */}
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 'var(--spacing-4)',
}}
>
<div style={{ maxWidth: '250px' }}>
<SelectField
label=""
name="statusFilter"
value={statusFilter}
onChange={setStatusFilter}
options={[
{ value: '', label: 'All Statuses' },
{ value: 'pending', label: 'Pending' },
{ value: 'brand_pending', label: 'Brand Pending' },
{ value: 'brand_approved', label: 'Brand Approved' },
{ value: 'campaign_approved', label: 'Campaign Approved' },
{ value: 'failed', label: 'Failed' },
{ value: 'remediation', label: 'Auto-Fixing' },
]}
/>
</div>
<div style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-secondary)' }}>
Showing {filteredSubmissions.length} of {submissions.length} submissions
</div>
</div>
{/* Data Table */}
<DataTable
columns={columns}
data={filteredSubmissions}
onRowClick={handleRowClick}
emptyMessage="No submissions yet. Create your first registration to get started."
/>
</div>
</div>
);
}