UI Phase 2 (L0): shared layout primitives + chrome standardization

Extract four shared layout widgets from the best existing implementations and
adopt them across list/overview screens, deleting duplicated inline code.
Layout/consistency only — no behavior change.

New primitives (lib/widgets/):
- app_section_header.dart  — AppSectionHeader (azure accent bar + uppercase
  telemetry label; promoted from the dashboard's _sectionTitle).
- app_metric_card.dart     — AppMetricCard (uppercase label + mono value on a
  flat hairline surface; unifies KPI/stat tiles).
- app_status_summary.dart  — AppStatusSummaryRow/Item/Chip (dot + count + label,
  selectable; semantic AppStatusColors).
- app_filter_bar.dart      — AppFilterBar (responsive filter-row chrome).

Adopted / de-duplicated:
- Dashboard: _MetricCard now wraps AppMetricCard; section titles use
  AppSectionHeader (removed local _sectionTitle).
- Tickets / Tasks / ISR: three separate summary-chip implementations
  (_StatusSummaryCard x2, _SummaryChip) collapsed into AppStatusSummaryRow.
- Network Map overview: Sites/Devices tiles -> AppMetricCard (removed local
  _SummaryCard).
- Workforce: "Duty Schedules" header -> AppSectionHeader with trailing chip.

Verified: flutter analyze lib clean; flutter build web green; visual QA (tickets
+ ISR summary chips) in dark.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-09-26 15:42:26 +08:00
parent 57377b1b21
commit 9f764150a2
10 changed files with 428 additions and 325 deletions
+10 -57
View File
@@ -30,6 +30,7 @@ import '../../widgets/tasq_adaptive_list.dart';
import '../../widgets/typing_dots.dart';
import '../../theme/app_surfaces.dart';
import '../../theme/app_status_colors.dart';
import '../../widgets/app_status_summary.dart';
import '../../utils/snackbar.dart';
import '../../widgets/app_breakpoints.dart';
import '../../widgets/app_page_header.dart';
@@ -1329,63 +1330,15 @@ class _StatusSummaryRow extends StatelessWidget {
final entries = counts.entries.toList()
..sort((a, b) => a.key.compareTo(b.key));
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
for (int i = 0; i < entries.length; i++) ...[
if (i > 0) const SizedBox(width: 8),
_StatusSummaryCard(
status: entries[i].key,
count: entries[i].value,
),
],
],
),
);
}
}
class _StatusSummaryCard extends StatelessWidget {
const _StatusSummaryCard({required this.status, required this.count});
final String status;
final int count;
@override
Widget build(BuildContext context) {
final swatch = AppStatusColors.of(context).byKind(_taskStatusKind(status));
final background = swatch.container;
final foreground = swatch.onContainer;
final label = status.replaceAll('_', ' ').toUpperCase();
return Material(
color: background,
borderRadius: BorderRadius.circular(AppSurfaces.of(context).chipRadius),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
label,
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: foreground,
fontWeight: FontWeight.w600,
letterSpacing: 0.4,
),
),
const SizedBox(width: 6),
Text(
count.toString(),
style: Theme.of(context).textTheme.labelLarge?.copyWith(
color: foreground,
fontWeight: FontWeight.w700,
),
),
],
),
),
return AppStatusSummaryRow(
items: [
for (final e in entries)
AppStatusSummaryItem(
label: e.key.replaceAll('_', ' '),
count: e.value,
kind: _taskStatusKind(e.key),
),
],
);
}
}