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
@@ -7,6 +7,7 @@ import '../../providers/network_map/network_devices_provider.dart';
import '../../providers/network_map/network_sites_provider.dart';
import '../../providers/profile_provider.dart';
import '../../utils/snackbar.dart';
import '../../widgets/app_metric_card.dart';
import '../../widgets/app_page_header.dart';
import '../../widgets/app_state_view.dart';
import '../../widgets/responsive_body.dart';
@@ -167,41 +168,24 @@ class _SummaryRow extends StatelessWidget {
Widget build(BuildContext context) {
return Row(
children: [
Expanded(child: _SummaryCard(label: 'Sites', value: siteCount.toString(), icon: Icons.location_city_outlined)),
const SizedBox(width: 12),
Expanded(child: _SummaryCard(label: 'Devices', value: deviceCount.toString(), icon: Icons.devices_outlined)),
],
);
}
}
class _SummaryCard extends StatelessWidget {
const _SummaryCard({required this.label, required this.value, required this.icon});
final String label;
final String value;
final IconData icon;
@override
Widget build(BuildContext context) {
final tt = Theme.of(context).textTheme;
final cs = Theme.of(context).colorScheme;
return Card(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Row(
children: [
Icon(icon, color: cs.primary),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: tt.labelSmall?.copyWith(color: cs.onSurfaceVariant)),
Text(value, style: tt.headlineSmall?.copyWith(fontWeight: FontWeight.w700)),
],
),
],
Expanded(
child: AppMetricCard(
title: 'Sites',
value: siteCount.toString(),
icon: Icons.location_city_outlined,
animateValue: false,
),
),
),
const SizedBox(width: 12),
Expanded(
child: AppMetricCard(
title: 'Devices',
value: deviceCount.toString(),
icon: Icons.devices_outlined,
animateValue: false,
),
),
],
);
}
}