1fe9653bdf
Co-Authored-By: claude-flow <ruv@ruv.net>
158 lines
4.9 KiB
Dart
158 lines
4.9 KiB
Dart
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<ProgrammerDaySheetEvent> events;
|
|
|
|
/// Map of userId → display name used to show actor names.
|
|
final Map<String, String> 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,
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|