UI Phase 2: dogfood follow-ups — section headers, role terms, status semantics
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 <ruv@ruv.net>
This commit is contained in:
@@ -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<UserManagementScreen> {
|
||||
),
|
||||
TasQColumn<Profile>(
|
||||
header: 'Role',
|
||||
cellBuilder: (context, profile) => Text(profile.role),
|
||||
cellBuilder: (context, profile) => Text(_roleLabel(profile.role)),
|
||||
),
|
||||
TasQColumn<Profile>(
|
||||
header: 'Offices',
|
||||
@@ -245,7 +247,7 @@ class _UserManagementScreenState extends ConsumerState<UserManagementScreen> {
|
||||
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<UserManagementScreen> {
|
||||
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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user