209 lines
6.1 KiB
TypeScript
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>
|
|
);
|
|
}
|