import 'package:flutter/material.dart'; import '../data/mock.dart'; class AuditPage extends StatefulWidget { const AuditPage({super.key}); @override State createState() => _AuditPageState(); } class _AuditPageState extends State { String _typeFilter = 'all'; @override Widget build(BuildContext context) { final theme = Theme.of(context); final filtered = _typeFilter == 'all' ? mockEvents : mockEvents.where((e) => e.type.startsWith(_typeFilter)).toList(); return Scaffold( appBar: AppBar( title: const Text('Audit Stream'), centerTitle: false, actions: [ Padding( padding: const EdgeInsets.only(right: 16), child: DropdownButton( value: _typeFilter, onChanged: (v) => setState(() => _typeFilter = v ?? 'all'), items: const [ DropdownMenuItem(value: 'all', child: Text('all events')), DropdownMenuItem(value: 'flow.', child: Text('flow.*')), DropdownMenuItem(value: 'step.', child: Text('step.*')), DropdownMenuItem(value: 'module.', child: Text('module.*')), ], ), ), ], ), body: Column( children: [ _StatusBar(eventCount: filtered.length), Expanded( child: ListView.separated( padding: const EdgeInsets.all(24), itemCount: filtered.length, separatorBuilder: (_, _) => const SizedBox(height: 8), itemBuilder: (context, i) { final e = filtered[i]; final ts = e.timestamp; final tsStr = '${ts.hour.toString().padLeft(2, '0')}:${ts.minute.toString().padLeft(2, '0')}:${ts.second.toString().padLeft(2, '0')}'; final tone = _toneFor(e.type); return Card( elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide( color: theme.colorScheme.outlineVariant, ), ), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), child: Row( children: [ Container( width: 4, height: 32, decoration: BoxDecoration( color: tone, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 12), SizedBox( width: 90, child: Text( tsStr, style: theme.textTheme.bodySmall?.copyWith( fontFamily: 'monospace', color: theme.colorScheme.onSurfaceVariant, ), ), ), SizedBox( width: 180, child: Text( e.type, style: theme.textTheme.bodyMedium?.copyWith( fontFamily: 'monospace', fontWeight: FontWeight.w500, ), ), ), Expanded( child: Text( _contextLine(e), style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), ), if (e.durationMs != null) Text( '${e.durationMs}ms', style: theme.textTheme.bodySmall?.copyWith( fontFamily: 'monospace', color: theme.colorScheme.onSurfaceVariant, ), ), ], ), ), ); }, ), ), ], ), ); } String _contextLine(AuditEvent e) { final parts = []; if (e.flowName != null) parts.add(e.flowName!); if (e.stepId != null) parts.add(':${e.stepId}'); if (e.moduleName != null) parts.add(' via ${e.moduleName}'); if (e.error != null) parts.add(' [error: ${e.error}]'); return parts.join(''); } Color _toneFor(String type) { final scheme = Theme.of(context).colorScheme; if (type.endsWith('.failed')) return scheme.error; if (type.endsWith('.completed')) return scheme.primary; if (type.endsWith('.started')) return scheme.tertiary; return scheme.outline; } } class _StatusBar extends StatelessWidget { final int eventCount; const _StatusBar({required this.eventCount}); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), color: theme.colorScheme.surfaceContainerHigh, child: Row( children: [ Icon( Icons.fiber_manual_record, size: 12, color: theme.colorScheme.tertiary, ), const SizedBox(width: 8), Text( 'live (mock data) — $eventCount events', style: theme.textTheme.bodySmall, ), const Spacer(), Text( 'hash chain: verified', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.primary, ), ), ], ), ); } }