/ dashboard-v2 / frontend / src / pages / Quality.jsx
Quality.jsx
  1  import { useQuery } from '@tanstack/react-query';
  2  import { fetchQuality } from '../api';
  3  import MetricCard from '../components/MetricCard';
  4  import DataTable from '../components/DataTable';
  5  import Tabs from '../components/Tabs';
  6  import { BarChart, LineChart, GaugeChart } from '../components/charts';
  7  
  8  const TASK_COLS = [
  9    { accessorKey: 'task_type', header: 'Type' },
 10    {
 11      accessorKey: 'status',
 12      header: 'Status',
 13      cell: i => {
 14        const v = i.getValue();
 15        const color =
 16          {
 17            completed: 'text-emerald-400',
 18            pending: 'text-slate-400',
 19            in_progress: 'text-sky-400',
 20            failed: 'text-red-400',
 21            blocked: 'text-amber-400',
 22            cancelled: 'text-slate-500',
 23          }[v] ?? 'text-slate-400';
 24        return <span className={color}>{v}</span>;
 25      },
 26    },
 27    { accessorKey: 'assigned_agent', header: 'Agent' },
 28    {
 29      accessorKey: 'created_at',
 30      header: 'Created',
 31      cell: i => i.getValue()?.slice(0, 16).replace('T', ' ') ?? '—',
 32    },
 33  ];
 34  
 35  const LOG_COLS = [
 36    { accessorKey: 'agent_type', header: 'Agent' },
 37    {
 38      accessorKey: 'level',
 39      header: 'Level',
 40      cell: i => {
 41        const v = i.getValue();
 42        const color =
 43          v === 'error' ? 'text-red-400' : v === 'warn' ? 'text-amber-400' : 'text-slate-400';
 44        return <span className={color}>{v}</span>;
 45      },
 46    },
 47    {
 48      accessorKey: 'message',
 49      header: 'Message',
 50      cell: i => (
 51        <span className="text-xs text-slate-300 truncate max-w-md block">{i.getValue()}</span>
 52      ),
 53    },
 54    { accessorKey: 'created_at', header: 'Time', cell: i => i.getValue()?.slice(11, 19) ?? '—' },
 55  ];
 56  
 57  const COVERAGE_COLS = [
 58    { accessorKey: 'file', header: 'File' },
 59    {
 60      accessorKey: 'statements',
 61      header: 'Stmts %',
 62      cell: i => {
 63        const v = i.getValue() ?? 0;
 64        const color = v >= 85 ? 'text-emerald-400' : v >= 60 ? 'text-amber-400' : 'text-red-400';
 65        return <span className={color}>{v.toFixed(1)}%</span>;
 66      },
 67    },
 68    {
 69      accessorKey: 'branches',
 70      header: 'Branch %',
 71      cell: i => {
 72        const v = i.getValue() ?? 0;
 73        const color = v >= 85 ? 'text-emerald-400' : v >= 60 ? 'text-amber-400' : 'text-red-400';
 74        return <span className={color}>{v.toFixed(1)}%</span>;
 75      },
 76    },
 77    {
 78      accessorKey: 'functions',
 79      header: 'Func %',
 80      cell: i => {
 81        const v = i.getValue() ?? 0;
 82        const color = v >= 85 ? 'text-emerald-400' : v >= 60 ? 'text-amber-400' : 'text-red-400';
 83        return <span className={color}>{v.toFixed(1)}%</span>;
 84      },
 85    },
 86  ];
 87  
 88  export default function Quality() {
 89    const { data, isLoading, error } = useQuery({ queryKey: ['quality'], queryFn: fetchQuality });
 90  
 91    if (isLoading) return <div className="text-slate-400 p-8">Loading…</div>;
 92    if (error) return <div className="text-red-400 p-8">Error: {error.message}</div>;
 93  
 94    const d = data ?? {};
 95    const ag = d.agent_state ?? {};
 96    const tasks = d.agent_tasks ?? [];
 97    const logs = d.agent_logs ?? [];
 98    const perf = d.agent_performance_7d ?? [];
 99    const cost24h = d.agent_cost_24h ?? {};
100    const cov = d.coverage_data ?? {};
101    const tests = d.test_results ?? {};
102  
103    const agentSummary = Object.entries(ag).map(([name, state]) => ({
104      name,
105      status: state?.status ?? 'unknown',
106      invocations: state?.invocations_24h ?? 0,
107      failures: state?.failures_24h ?? 0,
108    }));
109  
110    return (
111      <div className="space-y-6">
112        <Tabs tabs={['Agent System', 'Code Coverage']}>
113          {active =>
114            active === 'Agent System' ? (
115              <div className="space-y-6">
116                {/* Agent metrics */}
117                <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
118                  <MetricCard
119                    label="Active Agents"
120                    value={agentSummary.filter(a => a.status === 'active').length}
121                  />
122                  <MetricCard
123                    label="Tasks Pending"
124                    value={tasks.filter(t => t.status === 'pending').length}
125                    variant="warning"
126                  />
127                  <MetricCard
128                    label="Tasks Blocked"
129                    value={tasks.filter(t => t.status === 'blocked').length}
130                    variant={
131                      tasks.filter(t => t.status === 'blocked').length > 0 ? 'error' : 'success'
132                    }
133                  />
134                  <MetricCard
135                    label="Agent Cost (24h)"
136                    value={`$${(cost24h.total_usd ?? 0).toFixed(4)}`}
137                    variant={(cost24h.total_usd ?? 0) > 1 ? 'warning' : 'default'}
138                  />
139                </div>
140  
141                {/* Agent status grid */}
142                {agentSummary.length > 0 && (
143                  <div className="grid grid-cols-2 md:grid-cols-3 gap-3">
144                    {agentSummary.map(a => (
145                      <div
146                        key={a.name}
147                        className="bg-slate-800 rounded-lg p-3 border border-slate-700"
148                      >
149                        <div className="flex items-center justify-between">
150                          <span className="text-sm font-medium text-slate-300 capitalize">
151                            {a.name}
152                          </span>
153                          <span
154                            className={`text-xs px-1.5 py-0.5 rounded ${
155                              a.status === 'active'
156                                ? 'bg-emerald-900 text-emerald-300'
157                                : a.status === 'disabled'
158                                  ? 'bg-slate-700 text-slate-400'
159                                  : 'bg-amber-900 text-amber-300'
160                            }`}
161                          >
162                            {a.status}
163                          </span>
164                        </div>
165                        <div className="mt-1.5 text-xs text-slate-500">
166                          {a.invocations} runs · {a.failures} failed (24h)
167                        </div>
168                      </div>
169                    ))}
170                  </div>
171                )}
172  
173                {/* Performance 7d */}
174                {perf.length > 0 && (
175                  <div className="bg-slate-800 rounded-xl p-4 border border-slate-700">
176                    <h3 className="text-sm font-medium text-slate-400 mb-3">
177                      Agent Performance (7 Days)
178                    </h3>
179                    <LineChart
180                      data={perf}
181                      xKey="date"
182                      lines={[
183                        { key: 'success_rate', name: 'Success %', color: '#34d399' },
184                        { key: 'invocations', name: 'Invocations', color: '#60a5fa' },
185                      ]}
186                    />
187                  </div>
188                )}
189  
190                {/* Pending tasks */}
191                {tasks.length > 0 && (
192                  <div className="bg-slate-800 rounded-xl p-4 border border-slate-700">
193                    <h3 className="text-sm font-medium text-slate-400 mb-3">Recent Tasks</h3>
194                    <DataTable columns={TASK_COLS} data={tasks} maxRows={20} />
195                  </div>
196                )}
197  
198                {/* Agent logs */}
199                {logs.length > 0 && (
200                  <div className="bg-slate-800 rounded-xl p-4 border border-slate-700">
201                    <h3 className="text-sm font-medium text-slate-400 mb-3">Recent Agent Logs</h3>
202                    <DataTable columns={LOG_COLS} data={logs} maxRows={30} />
203                  </div>
204                )}
205              </div>
206            ) : (
207              <div className="space-y-6">
208                {/* Coverage gauges */}
209                <div className="grid grid-cols-3 gap-4">
210                  <div className="bg-slate-800 rounded-xl p-4 border border-slate-700">
211                    <GaugeChart
212                      value={cov.statements ?? 0}
213                      label="Statements"
214                      color={
215                        cov.statements >= 85
216                          ? '#34d399'
217                          : cov.statements >= 60
218                            ? '#fbbf24'
219                            : '#f87171'
220                      }
221                    />
222                  </div>
223                  <div className="bg-slate-800 rounded-xl p-4 border border-slate-700">
224                    <GaugeChart
225                      value={cov.branches ?? 0}
226                      label="Branches"
227                      color={
228                        cov.branches >= 85 ? '#34d399' : cov.branches >= 60 ? '#fbbf24' : '#f87171'
229                      }
230                    />
231                  </div>
232                  <div className="bg-slate-800 rounded-xl p-4 border border-slate-700">
233                    <GaugeChart
234                      value={cov.functions ?? 0}
235                      label="Functions"
236                      color={
237                        cov.functions >= 85 ? '#34d399' : cov.functions >= 60 ? '#fbbf24' : '#f87171'
238                      }
239                    />
240                  </div>
241                </div>
242  
243                {/* Test results */}
244                {tests.total > 0 && (
245                  <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
246                    <MetricCard label="Total Tests" value={tests.total ?? 0} />
247                    <MetricCard label="Passed" value={tests.passed ?? 0} variant="success" />
248                    <MetricCard
249                      label="Failed"
250                      value={tests.failed ?? 0}
251                      variant={(tests.failed ?? 0) > 0 ? 'error' : 'success'}
252                    />
253                    <MetricCard label="Skipped" value={tests.skipped ?? 0} />
254                  </div>
255                )}
256  
257                {/* Per-file coverage */}
258                {(cov.files ?? []).length > 0 && (
259                  <div className="bg-slate-800 rounded-xl p-4 border border-slate-700">
260                    <h3 className="text-sm font-medium text-slate-400 mb-3">Per-file Coverage</h3>
261                    <DataTable columns={COVERAGE_COLS} data={cov.files ?? []} maxRows={30} />
262                  </div>
263                )}
264  
265                {!cov.statements && (
266                  <div className="bg-slate-800 rounded-xl p-8 border border-slate-700 text-center">
267                    <p className="text-slate-500 text-sm">
268                      No coverage data found. Run{' '}
269                      <code className="bg-slate-900 px-1 rounded">npm test</code> to generate.
270                    </p>
271                  </div>
272                )}
273              </div>
274            )
275          }
276        </Tabs>
277      </div>
278    );
279  }