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
+18 -31
View File
@@ -23,6 +23,7 @@ import '../../widgets/responsive_body.dart';
import '../../widgets/tasq_adaptive_list.dart';
import '../../widgets/typing_dots.dart';
import '../../theme/app_surfaces.dart';
import '../../theme/app_status_colors.dart';
import '../../utils/snackbar.dart';
import '../../widgets/app_breakpoints.dart';
import '../../widgets/app_page_header.dart';
@@ -739,26 +740,14 @@ class _StatusSummaryCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final background = switch (status) {
'critical' => scheme.errorContainer,
'pending' => scheme.tertiaryContainer,
'promoted' => scheme.secondaryContainer,
'closed' => scheme.primaryContainer,
_ => scheme.surfaceContainerHigh,
};
final foreground = switch (status) {
'critical' => scheme.onErrorContainer,
'pending' => scheme.onTertiaryContainer,
'promoted' => scheme.onSecondaryContainer,
'closed' => scheme.onPrimaryContainer,
_ => scheme.onSurfaceVariant,
};
final swatch = AppStatusColors.of(context).byKind(_ticketStatusKind(status));
final background = swatch.container;
final foreground = swatch.onContainer;
final label = status.replaceAll('_', ' ').toUpperCase();
return Material(
color: background,
borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(AppSurfaces.of(context).chipRadius),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: Row(
@@ -787,6 +776,16 @@ class _StatusSummaryCard extends StatelessWidget {
}
}
/// Maps a ticket status to a semantic [StatusKind] so its color carries meaning
/// (red=critical, amber=pending, blue=promoted/escalated, green=closed).
StatusKind _ticketStatusKind(String status) => switch (status) {
'critical' => StatusKind.danger,
'pending' => StatusKind.warning,
'promoted' => StatusKind.info,
'closed' => StatusKind.success,
_ => StatusKind.neutral,
};
/// First 8 chars of the UUID, like a short git hash; the full id is in the
/// tooltip. Tickets have no human-friendly number.
String _shortId(String id) =>
@@ -813,21 +812,9 @@ class _StatusBadge extends StatelessWidget {
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final background = switch (status) {
'critical' => scheme.errorContainer,
'pending' => scheme.tertiaryContainer,
'promoted' => scheme.secondaryContainer,
'closed' => scheme.primaryContainer,
_ => scheme.surfaceContainerHighest,
};
final foreground = switch (status) {
'critical' => scheme.onErrorContainer,
'pending' => scheme.onTertiaryContainer,
'promoted' => scheme.onSecondaryContainer,
'closed' => scheme.onPrimaryContainer,
_ => scheme.onSurfaceVariant,
};
final swatch = AppStatusColors.of(context).byKind(_ticketStatusKind(status));
final background = swatch.container;
final foreground = swatch.onContainer;
return Badge(
backgroundColor: background,