import 'package:flutter/material.dart'; import '../../../models/programmer_day_sheet_event.model.dart'; import '../../../theme/app_status_colors.dart'; import '../../../utils/app_time.dart'; import '../../../widgets/app_section_header.dart'; /// Maps an event kind to a human-readable label. String _threadLabel(String kind) => switch (kind) { 'submitted' => 'Submitted', 'auto_submitted' => 'Auto-submitted at end of day', 'disapproved' => 'Disapproved', 'justified' => 'Justified', 'approved' => 'Approved', 'amended' => 'Reopened — new work after approval', _ => kind, }; IconData _threadIcon(String kind) => switch (kind) { 'submitted' => Icons.send_outlined, 'auto_submitted' => Icons.schedule_send_outlined, 'disapproved' => Icons.thumb_down_alt_outlined, 'justified' => Icons.chat_bubble_outline, 'approved' => Icons.check_circle_outline, 'amended' => Icons.redo_outlined, _ => Icons.circle_outlined, }; StatusKind _threadKind(String kind) => switch (kind) { 'approved' => StatusKind.success, 'disapproved' => StatusKind.warning, 'justified' => StatusKind.info, _ => StatusKind.neutral, }; /// Renders day-sheet events as a vertical timeline, oldest first. class DaySheetThread extends StatelessWidget { const DaySheetThread({ super.key, required this.events, required this.profileNames, }); final List events; /// Map of userId → display name used to show actor names. final Map profileNames; @override Widget build(BuildContext context) { final tt = Theme.of(context).textTheme; final cs = Theme.of(context).colorScheme; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const AppSectionHeader('Activity'), if (events.isEmpty) Text( 'No activity yet.', style: tt.bodySmall?.copyWith(color: cs.onSurfaceVariant), ) else for (final event in events) _ThreadRow( event: event, actorName: event.actorId != null ? profileNames[event.actorId] ?? 'Someone' : null, ), ], ); } } class _ThreadRow extends StatelessWidget { const _ThreadRow({required this.event, this.actorName}); final ProgrammerDaySheetEvent event; final String? actorName; @override Widget build(BuildContext context) { final tt = Theme.of(context).textTheme; final cs = Theme.of(context).colorScheme; final status = AppStatusColors.of(context); final kind = _threadKind(event.kind); final swatch = status.byKind(kind); return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 28, height: 28, decoration: BoxDecoration( color: swatch.container, shape: BoxShape.circle, ), child: Icon( _threadIcon(event.kind), size: 14, color: swatch.onContainer, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( actorName != null ? '$actorName · ${_threadLabel(event.kind)}' : _threadLabel(event.kind), style: tt.bodyMedium, ), ), Text( AppTime.relative(event.createdAt), style: tt.bodySmall ?.copyWith(color: cs.onSurfaceVariant), ), ], ), if (event.body != null && event.body!.isNotEmpty) ...[ const SizedBox(height: 2), Text( event.body!, style: tt.bodySmall ?.copyWith(color: cs.onSurfaceVariant), maxLines: 4, overflow: TextOverflow.ellipsis, ), ], if (event.flaggedTaskIds.isNotEmpty) ...[ const SizedBox(height: 2), Text( 'Flagged: ${event.flaggedTaskIds.length} task(s)', style: tt.bodySmall?.copyWith( color: cs.onSurfaceVariant, fontStyle: FontStyle.italic, ), ), ], ], ), ), ], ), ); } }