Adds three dark-first chip badges in the TierBadge pattern: - Jurisdiction (Berlin/DE/EU/International) — derived from the ELI, surfaced on the norms list, detail header and heatmap chips/hover. - Freshness (aktuell/geaendert/ungeprueft) — flags norms amended upstream (standDate + sourceSha256); renders only when stale. - Jurist review — marks evaluations a trusted jurist has confirmed (optional; the figure stands on its own). Models gain Jurisdiction/Freshness enums, Norm.freshness/supersededNote and Evaluation.reviewedBy/reviewedAt; HubRepository parses them from the flow bag (ELI fallback). HeatmapPage shows a real empty-state (hub-down vs no-evaluations) instead of a blank plot, and connect() no longer throws on a failed probe. Signed-off-by: flemming-it <sf@flemming.it>
77 lines
2.6 KiB
Dart
77 lines
2.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../data/models.dart';
|
|
import '../theme/reclaim_tokens.dart';
|
|
|
|
/// Review badge — marks an evaluation a trusted jurist has confirmed
|
|
/// (signed off via the hub approval). Jurist review is OPTIONAL: an
|
|
/// evaluation stands on its own; this only adds the "bestätigt" mark.
|
|
/// Renders a confirm-green check when reviewed; renders the muted
|
|
/// "ungeprüft" state only when [showUnreviewed] is set (otherwise
|
|
/// nothing, to keep unreviewed rows clean).
|
|
class ReviewBadge extends StatelessWidget {
|
|
const ReviewBadge({
|
|
super.key,
|
|
required this.evaluation,
|
|
this.compact = false,
|
|
this.showUnreviewed = false,
|
|
});
|
|
|
|
final Evaluation evaluation;
|
|
final bool compact;
|
|
final bool showUnreviewed;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final reviewed = evaluation.isReviewed;
|
|
if (!reviewed && !showUnreviewed) return const SizedBox.shrink();
|
|
|
|
final t = Theme.of(context).textTheme;
|
|
final color =
|
|
reviewed ? ReclaimColors.reviewConfirmed : ReclaimColors.mute;
|
|
final icon = reviewed
|
|
? Icons.verified_outlined
|
|
: Icons.radio_button_unchecked;
|
|
final label = reviewed
|
|
? (compact ? 'geprüft' : 'Jurist-geprüft')
|
|
: (compact ? 'offen' : 'ungeprüft');
|
|
final tip = reviewed
|
|
? 'Bestätigt von ${evaluation.reviewedBy}'
|
|
'${evaluation.reviewedAt != null ? ' am ${_d(evaluation.reviewedAt!)}' : ''}.\n'
|
|
'Jurist-Review ist optional — es bestätigt die Auswertung, '
|
|
'ist aber keine Voraussetzung.'
|
|
: 'Noch von keinem Juristen bestätigt. Die Auswertung gilt '
|
|
'trotzdem (Review ist optional).';
|
|
return Tooltip(
|
|
message: tip,
|
|
child: Container(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: compact ? 6 : ReclaimSpace.sm,
|
|
vertical: compact ? 2 : 4,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: color.withValues(alpha: 0.18),
|
|
border: Border.all(color: color.withValues(alpha: 0.55)),
|
|
borderRadius: const BorderRadius.all(ReclaimRadius.sm),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(icon, size: compact ? 12 : 14, color: color),
|
|
const SizedBox(width: 6),
|
|
Text(
|
|
label,
|
|
style: (compact ? t.labelSmall : t.labelLarge)?.copyWith(
|
|
color: color,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
static String _d(DateTime d) =>
|
|
'${d.day.toString().padLeft(2, '0')}.${d.month.toString().padLeft(2, '0')}.${d.year}';
|
|
}
|