UI: Cyber/IT design overhaul with semantic status tokens (Material 3)

Re-theme TasQ with a refined enterprise "Cyber/IT" identity on Material 3.
Functionality is unchanged — only color/shape tokens and a few state widgets.

Foundation:
- Hand-tuned light + dark ColorSchemes (deep-slate surfaces, azure/teal/violet
  accents), tightened 12-16dp shape language, flat hairline-outlined cards.
- New AppStatusColors theme extension (success/warning/danger/info/neutral,
  per-brightness) replacing scattered raw Colors.green/orange/red/grey so status
  color is consistent and legible in dark mode.
- New TechChip mono primitive; StatusPill.kind; M3Card hairline variants;
  tightened AppSurfaces radii.

Per-screen:
- Tokenized status colors + radii across dashboard, tickets, tasks, IT service
  requests, attendance, whereabouts, network map, reports charts, auth, and
  shared/admin screens. Added _ticketStatusKind/_taskStatusKind/_isrStatusKind
  mappers so status carries meaning consistently.
- Fixed white pie-slice borders for dark mode; dropped ad-hoc dialog radius
  overrides in favor of the theme default. Categorical palettes (leave-type
  icons, team color picker, distribution charts) intentionally preserved.

Verified: flutter analyze lib clean; flutter build web green; visual QA in
light and dark (dashboard, tickets, attendance).

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-09-26 15:13:47 +08:00
parent 3cb980b629
commit 57377b1b21
28 changed files with 986 additions and 470 deletions
@@ -29,6 +29,7 @@ import '../../widgets/gemini_animated_text_field.dart';
import '../../widgets/m3_card.dart';
import '../../widgets/mono_text.dart';
import '../../widgets/responsive_body.dart';
import '../../theme/app_status_colors.dart';
import '../../widgets/status_pill.dart';
import 'it_service_request_pdf.dart';
@@ -411,13 +412,13 @@ class _ItServiceRequestDetailScreenState
),
)
: _hasRecentSave
? const Padding(
key: ValueKey('isr_saved'),
padding: EdgeInsets.symmetric(horizontal: 8),
? Padding(
key: const ValueKey('isr_saved'),
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Icon(
Icons.check_circle,
size: 18,
color: Colors.green,
color: AppStatusColors.of(context).success.color,
),
)
: const SizedBox(key: ValueKey('isr_idle')),
@@ -555,7 +556,10 @@ class _ItServiceRequestDetailScreenState
if (request.requestNumber != null)
MonoText(request.requestNumber!),
const SizedBox(width: 12),
StatusPill(label: ItServiceRequestStatus.label(request.status)),
StatusPill.kind(
_isrStatusKind(request.status),
label: ItServiceRequestStatus.label(request.status),
),
const Spacer(),
Text(
'Created ${AppTime.formatDate(request.createdAt)}',
@@ -1231,10 +1235,10 @@ class _ItServiceRequestDetailScreenState
child: const Icon(Icons.save, size: 14),
)
: isSaved
? const Icon(
? Icon(
Icons.check_circle,
key: ValueKey('label_saved'),
color: Colors.green,
key: const ValueKey('label_saved'),
color: AppStatusColors.of(context).success.color,
size: 14,
)
: const SizedBox(key: ValueKey('label_idle')),
@@ -1584,9 +1588,6 @@ class _ItServiceRequestDetailScreenState
final controller = TextEditingController();
return AlertDialog(
title: const Text('Cancellation Reason'),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(28),
),
content: TextField(
controller: controller,
decoration: const InputDecoration(
@@ -1836,7 +1837,6 @@ class _StaffPickerDialogState extends State<_StaffPickerDialog> {
.toList();
return AlertDialog(
title: const Text('Select IT Staff'),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(28)),
content: SizedBox(
width: 350,
height: 400,
@@ -2245,3 +2245,16 @@ class _EvidenceSectionState extends ConsumerState<_EvidenceSection> {
);
}
}
/// Maps an IT service request status to a semantic [StatusKind]
/// (amber=pending approval, blue=scheduled/in-progress, green=completed,
/// grey=cancelled).
StatusKind _isrStatusKind(String status) => switch (status) {
ItServiceRequestStatus.pendingApproval => StatusKind.warning,
ItServiceRequestStatus.scheduled => StatusKind.info,
ItServiceRequestStatus.inProgress => StatusKind.info,
ItServiceRequestStatus.inProgressDryRun => StatusKind.info,
ItServiceRequestStatus.completed => StatusKind.success,
ItServiceRequestStatus.cancelled => StatusKind.neutral,
_ => StatusKind.neutral,
};
@@ -17,6 +17,7 @@ import '../../providers/tickets_provider.dart';
import '../../theme/m3_motion.dart';
import '../../utils/app_time.dart';
import '../../utils/snackbar.dart';
import '../../theme/app_status_colors.dart';
import '../../widgets/m3_card.dart';
import '../../widgets/mono_text.dart';
import '../../widgets/reconnect_overlay.dart';
@@ -381,9 +382,6 @@ class _ItServiceRequestsListScreenState
builder: (ctx, setDialogState) {
return AlertDialog(
title: const Text('New IT Service Request'),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(28),
),
content: SizedBox(
width: 400,
child: SingleChildScrollView(
@@ -538,6 +536,7 @@ class _StatusSummaryRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final status = AppStatusColors.of(context);
final pending = requests
.where((r) => r.status == ItServiceRequestStatus.pendingApproval)
.length;
@@ -563,7 +562,7 @@ class _StatusSummaryRow extends StatelessWidget {
_SummaryChip(
label: 'Pending',
count: pending,
color: cs.tertiary,
color: status.warning.color,
selected: selectedStatus == ItServiceRequestStatus.pendingApproval,
onTap: () => onStatusTap(ItServiceRequestStatus.pendingApproval),
),
@@ -571,7 +570,7 @@ class _StatusSummaryRow extends StatelessWidget {
_SummaryChip(
label: 'Scheduled',
count: scheduled,
color: cs.primary,
color: status.info.color,
selected: selectedStatus == ItServiceRequestStatus.scheduled,
onTap: () => onStatusTap(ItServiceRequestStatus.scheduled),
),
@@ -587,7 +586,7 @@ class _StatusSummaryRow extends StatelessWidget {
_SummaryChip(
label: 'Completed',
count: completed,
color: Colors.green,
color: status.success.color,
selected: selectedStatus == ItServiceRequestStatus.completed,
onTap: () => onStatusTap(ItServiceRequestStatus.completed),
),