/ dashboard-v2 / frontend / src / pages / Compliance.jsx
Compliance.jsx
  1  import { useQuery } from '@tanstack/react-query';
  2  import { fetchCompliance } from '../api';
  3  import MetricCard from '../components/MetricCard';
  4  import DataTable from '../components/DataTable';
  5  import Tabs from '../components/Tabs';
  6  import { BarChart, LineChart, PieChart } from '../components/charts';
  7  
  8  const FEEDBACK_COLS = [
  9    { accessorKey: 'category', header: 'Category' },
 10    { accessorKey: 'count', header: 'Count' },
 11    { accessorKey: 'avg_score', header: 'Avg Score', cell: i => i.getValue()?.toFixed(2) ?? '—' },
 12  ];
 13  
 14  const OPTOUT_COLS = [
 15    { accessorKey: 'channel', header: 'Channel' },
 16    { accessorKey: 'count', header: 'Opt-outs' },
 17    { accessorKey: 'pct', header: '%', cell: i => `${(i.getValue() ?? 0).toFixed(1)}%` },
 18  ];
 19  
 20  const PLATFORM_COLS = [
 21    { accessorKey: 'platform', header: 'Platform' },
 22    { accessorKey: 'sent', header: 'Sent' },
 23    { accessorKey: 'delivered', header: 'Delivered' },
 24    { accessorKey: 'failed', header: 'Failed' },
 25    {
 26      accessorKey: 'delivery_rate',
 27      header: 'Delivery %',
 28      cell: i => `${(i.getValue() ?? 0).toFixed(1)}%`,
 29    },
 30  ];
 31  
 32  const VERSION_COLS = [
 33    { accessorKey: 'version', header: 'Version' },
 34    { accessorKey: 'approved', header: 'Approved' },
 35    { accessorKey: 'rejected', header: 'Rejected' },
 36    { accessorKey: 'pending', header: 'Pending' },
 37    {
 38      accessorKey: 'approval_rate',
 39      header: 'Approval %',
 40      cell: i => `${(i.getValue() ?? 0).toFixed(1)}%`,
 41    },
 42  ];
 43  
 44  export default function Compliance() {
 45    const { data, isLoading, error } = useQuery({
 46      queryKey: ['compliance'],
 47      queryFn: fetchCompliance,
 48    });
 49  
 50    if (isLoading) return <div className="text-slate-400 p-8">Loading…</div>;
 51    if (error) return <div className="text-red-400 p-8">Error: {error.message}</div>;
 52  
 53    const d = data ?? {};
 54    const os = d.optout_stats ?? {};
 55    const ph = d.platform_health ?? [];
 56    const as_ = d.approval_stats ?? {};
 57    const fbc = d.feedback_by_category ?? [];
 58    const at = d.approval_trend ?? [];
 59    const rf = d.recent_feedback ?? [];
 60    const pv = d.prompt_versions ?? [];
 61  
 62    return (
 63      <div className="space-y-6">
 64        <Tabs tabs={['Legal', 'Prompt Learning']}>
 65          {active =>
 66            active === 'Legal' ? (
 67              <div className="space-y-6">
 68                {/* Opt-out metrics */}
 69                <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
 70                  <MetricCard label="Total Opt-outs" value={os.total ?? 0} />
 71                  <MetricCard label="Email Opt-outs" value={os.email ?? 0} variant="warning" />
 72                  <MetricCard label="SMS Opt-outs" value={os.sms ?? 0} variant="warning" />
 73                  <MetricCard label="Form Opt-outs" value={os.form ?? 0} />
 74                </div>
 75  
 76                {/* Opt-out breakdown */}
 77                {(d.optout_breakdown ?? []).length > 0 && (
 78                  <div className="bg-slate-800 rounded-xl p-4 border border-slate-700">
 79                    <h3 className="text-sm font-medium text-slate-400 mb-3">Opt-outs by Channel</h3>
 80                    <DataTable columns={OPTOUT_COLS} data={d.optout_breakdown ?? []} />
 81                  </div>
 82                )}
 83  
 84                {/* Platform delivery health */}
 85                <div className="bg-slate-800 rounded-xl p-4 border border-slate-700">
 86                  <h3 className="text-sm font-medium text-slate-400 mb-3">
 87                    Platform Delivery Health
 88                  </h3>
 89                  <DataTable columns={PLATFORM_COLS} data={ph} />
 90                </div>
 91  
 92                {/* CAN-SPAM / TCPA checklist */}
 93                <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
 94                  <div className="bg-slate-800 rounded-xl p-4 border border-slate-700">
 95                    <h3 className="text-sm font-medium text-slate-400 mb-3">CAN-SPAM Checklist</h3>
 96                    <ul className="space-y-2 text-sm">
 97                      {[
 98                        'Sender ID in all emails',
 99                        'Physical address included',
100                        'Unsubscribe link in footer',
101                        'Opt-outs honoured within 10 days',
102                        'No deceptive subject lines',
103                      ].map(item => (
104                        <li key={item} className="flex items-center gap-2 text-emerald-400">
105                          <span>✓</span>
106                          <span className="text-slate-300">{item}</span>
107                        </li>
108                      ))}
109                    </ul>
110                  </div>
111                  <div className="bg-slate-800 rounded-xl p-4 border border-slate-700">
112                    <h3 className="text-sm font-medium text-slate-400 mb-3">TCPA Checklist</h3>
113                    <ul className="space-y-2 text-sm">
114                      {[
115                        'STOP opt-out instruction in SMS (US/CA only)',
116                        'Business hours enforcement (8am–9pm)',
117                        'Sender ID in all SMS',
118                        'No automated calls to reassigned numbers',
119                        'Rate: 1 SMS per 3 days per contact',
120                      ].map(item => (
121                        <li key={item} className="flex items-center gap-2 text-emerald-400">
122                          <span>✓</span>
123                          <span className="text-slate-300">{item}</span>
124                        </li>
125                      ))}
126                    </ul>
127                  </div>
128                </div>
129              </div>
130            ) : (
131              <div className="space-y-6">
132                {/* Approval metrics */}
133                <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
134                  <MetricCard
135                    label="Approval Rate"
136                    value={`${(as_.approval_rate ?? 0).toFixed(1)}%`}
137                    variant={(as_.approval_rate ?? 0) >= 80 ? 'success' : 'warning'}
138                  />
139                  <MetricCard label="Approved" value={as_.approved ?? 0} variant="success" />
140                  <MetricCard label="Rejected" value={as_.rejected ?? 0} variant="error" />
141                  <MetricCard label="Pending Review" value={as_.pending ?? 0} variant="warning" />
142                </div>
143  
144                {/* Approval trend */}
145                {at.length > 0 && (
146                  <div className="bg-slate-800 rounded-xl p-4 border border-slate-700">
147                    <h3 className="text-sm font-medium text-slate-400 mb-3">
148                      Approval Trend (30 days)
149                    </h3>
150                    <LineChart
151                      data={at}
152                      xKey="date"
153                      lines={[
154                        { key: 'approved', name: 'Approved', color: '#34d399' },
155                        { key: 'rejected', name: 'Rejected', color: '#f87171' },
156                      ]}
157                    />
158                  </div>
159                )}
160  
161                {/* Feedback by category */}
162                {fbc.length > 0 && (
163                  <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
164                    <div className="bg-slate-800 rounded-xl p-4 border border-slate-700">
165                      <h3 className="text-sm font-medium text-slate-400 mb-3">
166                        Feedback by Category
167                      </h3>
168                      <PieChart data={fbc} nameKey="category" valueKey="count" />
169                    </div>
170                    <div className="bg-slate-800 rounded-xl p-4 border border-slate-700">
171                      <h3 className="text-sm font-medium text-slate-400 mb-3">Category Breakdown</h3>
172                      <DataTable columns={FEEDBACK_COLS} data={fbc} />
173                    </div>
174                  </div>
175                )}
176  
177                {/* Prompt version history */}
178                {pv.length > 0 && (
179                  <div className="bg-slate-800 rounded-xl p-4 border border-slate-700">
180                    <h3 className="text-sm font-medium text-slate-400 mb-3">
181                      Prompt Version History
182                    </h3>
183                    <DataTable columns={VERSION_COLS} data={pv} />
184                  </div>
185                )}
186  
187                {/* Recent feedback */}
188                {rf.length > 0 && (
189                  <div className="bg-slate-800 rounded-xl p-4 border border-slate-700">
190                    <h3 className="text-sm font-medium text-slate-400 mb-3">Recent Feedback</h3>
191                    <div className="space-y-2">
192                      {rf.slice(0, 10).map((f, i) => (
193                        <div
194                          key={i}
195                          className="text-sm text-slate-300 bg-slate-900 rounded p-2 border border-slate-700"
196                        >
197                          <span className="text-slate-500 text-xs mr-2">
198                            {f.created_at?.slice(0, 10)}
199                          </span>
200                          {f.feedback}
201                        </div>
202                      ))}
203                    </div>
204                  </div>
205                )}
206              </div>
207            )
208          }
209        </Tabs>
210      </div>
211    );
212  }