From 468543040626c680e2d9522fbbb15657d406f0d1 Mon Sep 17 00:00:00 2001 From: Marc Rejohn Castillano Date: Sat, 26 Sep 2026 16:44:39 +0800 Subject: [PATCH] =?UTF-8?q?UI=20Phase=202:=20dogfood=20follow-ups=20?= =?UTF-8?q?=E2=80=94=20section=20headers,=20role=20terms,=20status=20seman?= =?UTF-8?q?tics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address the remaining concrete items flagged by the dogfood pass. Section headers (→ AppSectionHeader, azure accent bar + uppercase): - Attendance: "Today's Schedule", "Pass Slip History", and two "Pending Approvals" rows (were plain bold text / a manual tertiary accent bar). User Management: - Roles now display human-friendly labels (it_staff → "IT Staff", standard → "Standard", …) in the table cell, mobile tile and edit dropdown, instead of raw snake_case (Content/terminology consistency). - _StatusBadge is now semantic via AppStatusColors: Active → success (green), Locked → danger (red), else neutral — previously everything was teal, so a Locked account read the same as an Active one. Verified: flutter analyze lib clean; flutter build web green; visual QA (Attendance, User Management) in dark. Co-Authored-By: claude-flow --- lib/screens/admin/user_management_screen.dart | 38 +++++++--- lib/screens/attendance/attendance_screen.dart | 70 ++++--------------- 2 files changed, 39 insertions(+), 69 deletions(-) diff --git a/lib/screens/admin/user_management_screen.dart b/lib/screens/admin/user_management_screen.dart index cbdea12f..d3393e4e 100644 --- a/lib/screens/admin/user_management_screen.dart +++ b/lib/screens/admin/user_management_screen.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; + +import '../../theme/app_status_colors.dart'; import '../../theme/m3_motion.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; @@ -197,7 +199,7 @@ class _UserManagementScreenState extends ConsumerState { ), TasQColumn( header: 'Role', - cellBuilder: (context, profile) => Text(profile.role), + cellBuilder: (context, profile) => Text(_roleLabel(profile.role)), ), TasQColumn( header: 'Offices', @@ -245,7 +247,7 @@ class _UserManagementScreenState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 2), - Text('Role: ${profile.role}'), + Text('Role: ${_roleLabel(profile.role)}'), Text('Offices: $officesAssigned'), Text('Last active: ${_formatLastActiveLabel(lastActive)}'), const SizedBox(height: 4), @@ -386,7 +388,10 @@ class _UserManagementScreenState extends ConsumerState { key: ValueKey('role_${_selectedUserId ?? 'none'}'), initialValue: _selectedRole, items: _roles - .map((role) => DropdownMenuItem(value: role, child: Text(role))) + .map( + (role) => + DropdownMenuItem(value: role, child: Text(_roleLabel(role))), + ) .toList(), onChanged: (value) => setDialogState(() => _selectedRole = value), decoration: const InputDecoration(labelText: 'Role'), @@ -717,6 +722,16 @@ String _formatLastActiveLabel(DateTime? value) { return DateFormat('yyyy-MM-dd').format(value); } +/// Human-friendly display label for a stored role value. +String _roleLabel(String role) => switch (role) { + 'it_staff' => 'IT Staff', + 'standard' => 'Standard', + 'dispatcher' => 'Dispatcher', + 'programmer' => 'Programmer', + 'admin' => 'Admin', + _ => role.replaceAll('_', ' '), +}; + class _StatusBadge extends StatelessWidget { const _StatusBadge({required this.label}); @@ -724,14 +739,15 @@ class _StatusBadge extends StatelessWidget { @override Widget build(BuildContext context) { - final scheme = Theme.of(context).colorScheme; - final isError = label.toLowerCase().contains('error'); - final background = isError - ? scheme.errorContainer - : scheme.secondaryContainer; - final foreground = isError - ? scheme.onErrorContainer - : scheme.onSecondaryContainer; + final status = AppStatusColors.of(context); + final swatch = switch (label.toLowerCase()) { + 'active' => status.success, + 'locked' => status.danger, + final l when l.contains('error') => status.danger, + _ => status.neutral, + }; + final background = swatch.container; + final foreground = swatch.onContainer; return Badge( backgroundColor: background, diff --git a/lib/screens/attendance/attendance_screen.dart b/lib/screens/attendance/attendance_screen.dart index 82e8aafe..2112e481 100644 --- a/lib/screens/attendance/attendance_screen.dart +++ b/lib/screens/attendance/attendance_screen.dart @@ -36,6 +36,7 @@ import 'work_log_tab.dart'; import '../../theme/m3_motion.dart'; import '../../theme/app_surfaces.dart'; import '../../theme/app_status_colors.dart'; +import '../../widgets/app_section_header.dart'; import '../../utils/app_time.dart'; import '../../utils/device_security.dart'; import '../../utils/location_permission.dart'; @@ -885,11 +886,9 @@ class _CheckInTabState extends ConsumerState<_CheckInTab> { const SizedBox(height: 24), // Today's schedule - Text( + const AppSectionHeader( "Today's Schedule", - style: theme.textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w600, - ), + padding: EdgeInsets.zero, ), const SizedBox(height: 8), @@ -5704,24 +5703,9 @@ class _PassSlipTabState extends ConsumerState<_PassSlipTab> { // Pending slips for admin approval if (isAdmin) ...[ - Row( - children: [ - Container( - width: 4, - height: 20, - decoration: BoxDecoration( - color: colors.tertiary, - borderRadius: BorderRadius.circular(2), - ), - ), - const SizedBox(width: 10), - Text( - 'Pending Approvals', - style: theme.textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w600, - ), - ), - ], + const AppSectionHeader( + 'Pending Approvals', + padding: EdgeInsets.zero, ), const SizedBox(height: 8), for (int i = 0; i < pendingSlipList.length; i++) @@ -5755,24 +5739,9 @@ class _PassSlipTabState extends ConsumerState<_PassSlipTab> { ], // History - Row( - children: [ - Container( - width: 4, - height: 20, - decoration: BoxDecoration( - color: colors.outlineVariant, - borderRadius: BorderRadius.circular(2), - ), - ), - const SizedBox(width: 10), - Text( - 'Pass Slip History', - style: theme.textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w600, - ), - ), - ], + const AppSectionHeader( + 'Pass Slip History', + padding: EdgeInsets.zero, ), const SizedBox(height: 8), for (int i = 0; i < historySlipList.length; i++) @@ -6069,24 +6038,9 @@ class _LeaveTabState extends ConsumerState<_LeaveTab> { children: [ // ── Pending Approvals (admin only) ── if (isAdmin) ...[ - Row( - children: [ - Container( - width: 4, - height: 20, - decoration: BoxDecoration( - color: colors.tertiary, - borderRadius: BorderRadius.circular(2), - ), - ), - const SizedBox(width: 10), - Text( - 'Pending Approvals', - style: theme.textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w600, - ), - ), - ], + const AppSectionHeader( + 'Pending Approvals', + padding: EdgeInsets.zero, ), const SizedBox(height: 8), if (pendingApprovals.isEmpty)