theme changes
This commit is contained in:
parent
4360931be4
commit
03486ea3f4
@ -162,6 +162,20 @@ class AppTheme {
|
|||||||
),
|
),
|
||||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: colorScheme.primary,
|
||||||
|
foregroundColor: colorScheme.onPrimary,
|
||||||
|
disabledBackgroundColor: colorScheme.onSurface.withValues(alpha: 0.12),
|
||||||
|
disabledForegroundColor: colorScheme.onSurface.withValues(alpha: 0.38),
|
||||||
|
minimumSize: const Size(0, 48),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||||
|
textStyle: textTheme.labelLarge,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
filledButtonTheme: FilledButtonThemeData(
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
backgroundColor: colorScheme.primary,
|
||||||
|
foregroundColor: colorScheme.onPrimary,
|
||||||
minimumSize: const Size(0, 48),
|
minimumSize: const Size(0, 48),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||||
@ -170,18 +184,30 @@ class AppTheme {
|
|||||||
),
|
),
|
||||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
|
foregroundColor: colorScheme.secondary,
|
||||||
minimumSize: const Size(0, 48),
|
minimumSize: const Size(0, 48),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
side: BorderSide(color: colorScheme.secondary.withValues(alpha: 0.55)),
|
||||||
textStyle: textTheme.labelLarge,
|
textStyle: textTheme.labelLarge,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
textButtonTheme: TextButtonThemeData(
|
textButtonTheme: TextButtonThemeData(
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: colorScheme.secondary,
|
||||||
minimumSize: const Size(0, 48),
|
minimumSize: const Size(0, 48),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
textStyle: textTheme.labelLarge,
|
textStyle: textTheme.labelLarge,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
iconButtonTheme: IconButtonThemeData(
|
||||||
|
style: IconButton.styleFrom(
|
||||||
|
foregroundColor: colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
floatingActionButtonTheme: FloatingActionButtonThemeData(
|
||||||
|
backgroundColor: colorScheme.primary,
|
||||||
|
foregroundColor: colorScheme.onPrimary,
|
||||||
|
),
|
||||||
navigationRailTheme: NavigationRailThemeData(
|
navigationRailTheme: NavigationRailThemeData(
|
||||||
backgroundColor: isDark ? AppColors.darkSurface : AppColors.lightSurface,
|
backgroundColor: isDark ? AppColors.darkSurface : AppColors.lightSurface,
|
||||||
selectedIconTheme: IconThemeData(color: colorScheme.primary),
|
selectedIconTheme: IconThemeData(color: colorScheme.primary),
|
||||||
@ -215,7 +241,17 @@ class AppTheme {
|
|||||||
dataTextStyle: textTheme.bodyMedium,
|
dataTextStyle: textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
chipTheme: ChipThemeData(
|
chipTheme: ChipThemeData(
|
||||||
labelStyle: textTheme.labelSmall,
|
backgroundColor: colorScheme.secondaryContainer.withValues(alpha: 0.55),
|
||||||
|
selectedColor: colorScheme.secondary.withValues(alpha: 0.18),
|
||||||
|
disabledColor: colorScheme.onSurface.withValues(alpha: 0.08),
|
||||||
|
labelStyle: textTheme.labelSmall?.copyWith(
|
||||||
|
color: colorScheme.onSecondaryContainer,
|
||||||
|
),
|
||||||
|
secondaryLabelStyle: textTheme.labelSmall?.copyWith(
|
||||||
|
color: colorScheme.secondary,
|
||||||
|
),
|
||||||
|
side: BorderSide(color: colorScheme.secondary.withValues(alpha: 0.28)),
|
||||||
|
iconTheme: IconThemeData(color: colorScheme.secondary, size: 18),
|
||||||
),
|
),
|
||||||
dialogTheme: DialogThemeData(
|
dialogTheme: DialogThemeData(
|
||||||
titleTextStyle: textTheme.titleLarge,
|
titleTextStyle: textTheme.titleLarge,
|
||||||
|
|||||||
@ -1088,6 +1088,9 @@ class _AssetFormPanelState extends ConsumerState<AssetFormPanel> {
|
|||||||
},
|
},
|
||||||
icon: const Icon(Icons.add, size: 18),
|
icon: const Icon(Icons.add, size: 18),
|
||||||
label: const Text('Add Item'),
|
label: const Text('Add Item'),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: theme.colorScheme.primary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -339,7 +339,7 @@ class _FiltersBar extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final iconColor = theme.colorScheme.primary;
|
final iconColor = theme.colorScheme.secondary;
|
||||||
final resetButton = IconButton(
|
final resetButton = IconButton(
|
||||||
tooltip: 'Reset',
|
tooltip: 'Reset',
|
||||||
color: iconColor,
|
color: iconColor,
|
||||||
|
|||||||
@ -45,13 +45,13 @@ class DashboardScreen extends StatelessWidget {
|
|||||||
title: 'Allocated',
|
title: 'Allocated',
|
||||||
value: '—',
|
value: '—',
|
||||||
icon: Icons.assignment_ind_outlined,
|
icon: Icons.assignment_ind_outlined,
|
||||||
color: Colors.blue,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
KpiCard(
|
KpiCard(
|
||||||
title: 'Available',
|
title: 'Available',
|
||||||
value: '—',
|
value: '—',
|
||||||
icon: Icons.check_circle_outline,
|
icon: Icons.check_circle_outline,
|
||||||
color: Colors.green,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
),
|
),
|
||||||
KpiCard(
|
KpiCard(
|
||||||
title: 'Under Maintenance',
|
title: 'Under Maintenance',
|
||||||
|
|||||||
@ -17,7 +17,8 @@ class GrnStatusChip extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final (color, label) = _resolveStatus(status);
|
final secondary = Theme.of(context).colorScheme.secondary;
|
||||||
|
final (color, label) = _resolveStatus(status, secondary);
|
||||||
if (forTable) {
|
if (forTable) {
|
||||||
return TableStatusBadge(
|
return TableStatusBadge(
|
||||||
label: label,
|
label: label,
|
||||||
@ -43,14 +44,14 @@ class GrnStatusChip extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
(Color, String) _resolveStatus(String raw) {
|
(Color, String) _resolveStatus(String raw, Color secondary) {
|
||||||
switch (raw.toUpperCase()) {
|
switch (raw.toUpperCase()) {
|
||||||
case 'POSTED':
|
case 'POSTED':
|
||||||
return (Colors.green.shade700, grnStatusLabel(raw));
|
return (secondary, grnStatusLabel(raw));
|
||||||
case 'CANCELLED':
|
case 'CANCELLED':
|
||||||
return (Colors.grey.shade700, grnStatusLabel(raw));
|
return (Colors.grey.shade700, grnStatusLabel(raw));
|
||||||
default:
|
default:
|
||||||
return (Colors.blueGrey, grnStatusLabel(raw));
|
return (secondary, grnStatusLabel(raw));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,8 @@ class PoStatusChip extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final (color, label) = _resolveStatus(status);
|
final secondary = Theme.of(context).colorScheme.secondary;
|
||||||
|
final (color, label) = _resolveStatus(status, secondary);
|
||||||
if (forTable) {
|
if (forTable) {
|
||||||
return TableStatusBadge(
|
return TableStatusBadge(
|
||||||
label: label,
|
label: label,
|
||||||
@ -29,7 +30,7 @@ class PoStatusChip extends StatelessWidget {
|
|||||||
return _PoBadge(label: label, color: color, compact: compact);
|
return _PoBadge(label: label, color: color, compact: compact);
|
||||||
}
|
}
|
||||||
|
|
||||||
(Color, String) _resolveStatus(String raw) {
|
(Color, String) _resolveStatus(String raw, Color secondary) {
|
||||||
switch (raw.toUpperCase()) {
|
switch (raw.toUpperCase()) {
|
||||||
case 'DRAFT':
|
case 'DRAFT':
|
||||||
return (const Color(0xFF546E7A), poStatusLabel(raw));
|
return (const Color(0xFF546E7A), poStatusLabel(raw));
|
||||||
@ -38,17 +39,17 @@ class PoStatusChip extends StatelessWidget {
|
|||||||
case 'PENDING':
|
case 'PENDING':
|
||||||
return (const Color(0xFFE65100), poStatusLabel(raw));
|
return (const Color(0xFFE65100), poStatusLabel(raw));
|
||||||
case 'APPROVED':
|
case 'APPROVED':
|
||||||
return (const Color(0xFF2E7D32), poStatusLabel(raw));
|
return (secondary, poStatusLabel(raw));
|
||||||
case 'REJECTED':
|
case 'REJECTED':
|
||||||
return (const Color(0xFFC62828), poStatusLabel(raw));
|
return (const Color(0xFFC62828), poStatusLabel(raw));
|
||||||
case 'CANCELLED':
|
case 'CANCELLED':
|
||||||
return (const Color(0xFF616161), poStatusLabel(raw));
|
return (const Color(0xFF616161), poStatusLabel(raw));
|
||||||
case 'PARTIALLY_RECEIVED':
|
case 'PARTIALLY_RECEIVED':
|
||||||
return (const Color(0xFF00695C), poStatusLabel(raw));
|
return (secondary, poStatusLabel(raw));
|
||||||
case 'FULLY_RECEIVED':
|
case 'FULLY_RECEIVED':
|
||||||
return (const Color(0xFF283593), poStatusLabel(raw));
|
return (secondary, poStatusLabel(raw));
|
||||||
default:
|
default:
|
||||||
return (const Color(0xFF546E7A), poStatusLabel(raw));
|
return (secondary, poStatusLabel(raw));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,7 +68,7 @@ class PoRevisionChip extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return _PoBadge(
|
return _PoBadge(
|
||||||
label: 'Revision $revisionNo',
|
label: 'Revision $revisionNo',
|
||||||
color: const Color(0xFF607D8B),
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
compact: compact,
|
compact: compact,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1511,18 +1511,34 @@ class _PermissionMatrixTable extends ConsumerWidget {
|
|||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
enableSearch: false,
|
enableSearch: false,
|
||||||
cellBuilder: (context, module) {
|
cellBuilder: (context, module) {
|
||||||
|
final moduleCatalog = catalog
|
||||||
|
?.where(
|
||||||
|
(entry) =>
|
||||||
|
entry.id == module.moduleId ||
|
||||||
|
normalizePermissionModuleCode(entry.code) ==
|
||||||
|
normalizePermissionModuleCode(module.code),
|
||||||
|
)
|
||||||
|
.firstOrNull;
|
||||||
|
final applicable = isPermissionActionApplicable(
|
||||||
|
module.code,
|
||||||
|
action,
|
||||||
|
catalogActions: moduleCatalog?.actions,
|
||||||
|
columnActions: actionColumns,
|
||||||
|
);
|
||||||
final checked = module.granted[action] ?? false;
|
final checked = module.granted[action] ?? false;
|
||||||
return Checkbox(
|
return Checkbox(
|
||||||
value: checked,
|
value: applicable ? checked : false,
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
onChanged: (value) => ref
|
onChanged: applicable
|
||||||
.read(permissionMatrixProvider(roleId).notifier)
|
? (value) => ref
|
||||||
.toggleAction(
|
.read(permissionMatrixProvider(roleId).notifier)
|
||||||
module.moduleId,
|
.toggleAction(
|
||||||
action,
|
module.moduleId,
|
||||||
value ?? false,
|
action,
|
||||||
),
|
value ?? false,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@ -126,11 +126,11 @@ class RoleBadge extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final primary = Theme.of(context).colorScheme.primary;
|
final secondary = Theme.of(context).colorScheme.secondary;
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: primary.withValues(alpha: 0.08),
|
color: secondary.withValues(alpha: 0.08),
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -139,7 +139,7 @@ class RoleBadge extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
width: 6,
|
width: 6,
|
||||||
height: 6,
|
height: 6,
|
||||||
decoration: BoxDecoration(color: primary, shape: BoxShape.circle),
|
decoration: BoxDecoration(color: secondary, shape: BoxShape.circle),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Flexible(
|
Flexible(
|
||||||
@ -148,7 +148,7 @@ class RoleBadge extends StatelessWidget {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||||
color: primary,
|
color: secondary,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -515,7 +515,7 @@ class _FiltersBarState extends State<_FiltersBar> {
|
|||||||
? (_moreOpen ? 'Less filters ($moreCount)' : 'More filters ($moreCount)')
|
? (_moreOpen ? 'Less filters ($moreCount)' : 'More filters ($moreCount)')
|
||||||
: (_moreOpen ? 'Less filters' : 'More filters');
|
: (_moreOpen ? 'Less filters' : 'More filters');
|
||||||
|
|
||||||
final iconColor = theme.colorScheme.primary;
|
final iconColor = theme.colorScheme.secondary;
|
||||||
|
|
||||||
final moreButton = IconButton(
|
final moreButton = IconButton(
|
||||||
tooltip: moreTooltip,
|
tooltip: moreTooltip,
|
||||||
|
|||||||
@ -164,6 +164,13 @@ class PermissionMatrixNotifier
|
|||||||
|
|
||||||
final updated = current.modules.map((row) {
|
final updated = current.modules.map((row) {
|
||||||
if (row.moduleId != moduleId) return row;
|
if (row.moduleId != moduleId) return row;
|
||||||
|
if (!isPermissionActionApplicable(
|
||||||
|
row.code,
|
||||||
|
action,
|
||||||
|
columnActions: current.actionColumns,
|
||||||
|
)) {
|
||||||
|
return row;
|
||||||
|
}
|
||||||
final granted = Map<String, bool>.from(row.granted);
|
final granted = Map<String, bool>.from(row.granted);
|
||||||
granted[action] = value;
|
granted[action] = value;
|
||||||
return row.copyWith(granted: granted);
|
return row.copyWith(granted: granted);
|
||||||
|
|||||||
@ -100,14 +100,23 @@ class _MatrixGrid extends ConsumerWidget {
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
enableSearch: false,
|
enableSearch: false,
|
||||||
cellBuilder: (_, row) => Checkbox(
|
cellBuilder: (_, row) {
|
||||||
value: row.granted[action] ?? false,
|
final applicable = isPermissionActionApplicable(
|
||||||
visualDensity: VisualDensity.compact,
|
row.code,
|
||||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
action,
|
||||||
onChanged: (checked) => ref
|
columnActions: matrix.actionColumns,
|
||||||
.read(permissionMatrixProvider(roleId).notifier)
|
);
|
||||||
.toggleAction(row.moduleId, action, checked ?? false),
|
return Checkbox(
|
||||||
),
|
value: applicable ? (row.granted[action] ?? false) : false,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
onChanged: applicable
|
||||||
|
? (checked) => ref
|
||||||
|
.read(permissionMatrixProvider(roleId).notifier)
|
||||||
|
.toggleAction(row.moduleId, action, checked ?? false)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -134,13 +143,22 @@ class _MatrixCardList extends ConsumerWidget {
|
|||||||
title: Text(row.name),
|
title: Text(row.name),
|
||||||
children: matrix.actionColumns
|
children: matrix.actionColumns
|
||||||
.map(
|
.map(
|
||||||
(action) => _PermissionSwitch(
|
(action) {
|
||||||
label: permissionActionLabel(action),
|
final applicable = isPermissionActionApplicable(
|
||||||
value: row.granted[action] ?? false,
|
row.code,
|
||||||
onChanged: (v) => ref
|
action,
|
||||||
.read(permissionMatrixProvider(roleId).notifier)
|
columnActions: matrix.actionColumns,
|
||||||
.toggleAction(row.moduleId, action, v),
|
);
|
||||||
),
|
return _PermissionSwitch(
|
||||||
|
label: permissionActionLabel(action),
|
||||||
|
value: applicable ? (row.granted[action] ?? false) : false,
|
||||||
|
onChanged: applicable
|
||||||
|
? (v) => ref
|
||||||
|
.read(permissionMatrixProvider(roleId).notifier)
|
||||||
|
.toggleAction(row.moduleId, action, v)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
@ -159,7 +177,7 @@ class _PermissionSwitch extends StatelessWidget {
|
|||||||
|
|
||||||
final String label;
|
final String label;
|
||||||
final bool value;
|
final bool value;
|
||||||
final ValueChanged<bool> onChanged;
|
final ValueChanged<bool>? onChanged;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@ -32,6 +32,54 @@ List<String> sortPermissionActions(Iterable<String> actions) {
|
|||||||
return permissionMatrixActionOrder.where(set.contains).toList();
|
return permissionMatrixActionOrder.where(set.contains).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String normalizePermissionModuleCode(String code) =>
|
||||||
|
code.trim().toLowerCase().replaceAll('-', '_').replaceAll(' ', '_');
|
||||||
|
|
||||||
|
/// Actions that apply to a module in the permission matrix UI.
|
||||||
|
///
|
||||||
|
/// Restricted modules omit actions that are not meaningful for them.
|
||||||
|
/// For other modules, [catalogActions] (when provided) limits the set.
|
||||||
|
List<String> applicablePermissionActionsForModule(
|
||||||
|
String code, {
|
||||||
|
List<String>? catalogActions,
|
||||||
|
List<String> columnActions = permissionMatrixActionOrder,
|
||||||
|
}) {
|
||||||
|
final normalized = normalizePermissionModuleCode(code);
|
||||||
|
final columns = sortPermissionActions(columnActions);
|
||||||
|
|
||||||
|
final restricted = switch (normalized) {
|
||||||
|
'settings' || 'setting' || 'settings_module' => const ['view', 'edit'],
|
||||||
|
'reports' || 'report' => const ['view', 'export'],
|
||||||
|
'audit_logs' || 'audit_log' || 'audit' => const ['view', 'export'],
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (restricted != null) {
|
||||||
|
return columns.where(restricted.contains).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (catalogActions != null && catalogActions.isNotEmpty) {
|
||||||
|
final catalogSet =
|
||||||
|
catalogActions.map((action) => action.toLowerCase()).toSet();
|
||||||
|
return columns.where(catalogSet.contains).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isPermissionActionApplicable(
|
||||||
|
String moduleCode,
|
||||||
|
String action, {
|
||||||
|
List<String>? catalogActions,
|
||||||
|
List<String> columnActions = permissionMatrixActionOrder,
|
||||||
|
}) {
|
||||||
|
return applicablePermissionActionsForModule(
|
||||||
|
moduleCode,
|
||||||
|
catalogActions: catalogActions,
|
||||||
|
columnActions: columnActions,
|
||||||
|
).contains(action.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
/// Module row from `GET /roles/permissions`.
|
/// Module row from `GET /roles/permissions`.
|
||||||
class PermissionModuleCatalog {
|
class PermissionModuleCatalog {
|
||||||
const PermissionModuleCatalog({
|
const PermissionModuleCatalog({
|
||||||
@ -181,12 +229,19 @@ class RolePermissionMatrix {
|
|||||||
return {
|
return {
|
||||||
'matrix': modules
|
'matrix': modules
|
||||||
.map(
|
.map(
|
||||||
(module) => {
|
(module) {
|
||||||
'module_id': int.tryParse(module.moduleId) ?? module.moduleId,
|
final applicable = applicablePermissionActionsForModule(
|
||||||
'actions': {
|
module.code,
|
||||||
for (final action in actionColumns)
|
columnActions: actionColumns,
|
||||||
action: module.granted[action] ?? false,
|
).toSet();
|
||||||
},
|
return {
|
||||||
|
'module_id': int.tryParse(module.moduleId) ?? module.moduleId,
|
||||||
|
'actions': {
|
||||||
|
for (final action in actionColumns)
|
||||||
|
action: applicable.contains(action) &&
|
||||||
|
(module.granted[action] ?? false),
|
||||||
|
},
|
||||||
|
};
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
|||||||
@ -20,11 +20,19 @@ class AppButton extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final primary = theme.colorScheme.primary;
|
||||||
|
final onPrimary = theme.colorScheme.onPrimary;
|
||||||
|
final secondary = theme.colorScheme.secondary;
|
||||||
|
|
||||||
final child = isLoading
|
final child = isLoading
|
||||||
? const SizedBox(
|
? SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
width: 20,
|
width: 20,
|
||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
color: isOutlined ? secondary : onPrimary,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
: Row(
|
: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -36,8 +44,22 @@ class AppButton extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final button = isOutlined
|
final button = isOutlined
|
||||||
? OutlinedButton(onPressed: isLoading ? null : onPressed, child: child)
|
? OutlinedButton(
|
||||||
: ElevatedButton(onPressed: isLoading ? null : onPressed, child: child);
|
onPressed: isLoading ? null : onPressed,
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
foregroundColor: secondary,
|
||||||
|
side: BorderSide(color: secondary.withValues(alpha: 0.55)),
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
)
|
||||||
|
: ElevatedButton(
|
||||||
|
onPressed: isLoading ? null : onPressed,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: primary,
|
||||||
|
foregroundColor: onPrimary,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
|
||||||
return expand ? SizedBox(width: double.infinity, child: button) : button;
|
return expand ? SizedBox(width: double.infinity, child: button) : button;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -453,7 +453,7 @@ class _ColumnSearchField extends StatelessWidget {
|
|||||||
Icons.search_rounded,
|
Icons.search_rounded,
|
||||||
size: 16,
|
size: 16,
|
||||||
color: hasQuery
|
color: hasQuery
|
||||||
? theme.colorScheme.primary
|
? theme.colorScheme.secondary
|
||||||
: theme.colorScheme.onSurfaceVariant.withValues(alpha: 0.7),
|
: theme.colorScheme.onSurfaceVariant.withValues(alpha: 0.7),
|
||||||
),
|
),
|
||||||
prefixIconConstraints: const BoxConstraints(
|
prefixIconConstraints: const BoxConstraints(
|
||||||
|
|||||||
@ -63,7 +63,7 @@ class AppFilterDateField extends StatelessWidget {
|
|||||||
height: 24,
|
height: 24,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, size: 16, color: theme.colorScheme.primary),
|
Icon(icon, size: 16, color: theme.colorScheme.secondary),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
@ -14,7 +14,7 @@ class AppSearchFilterButton extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final activeColor = theme.colorScheme.primary;
|
final activeColor = theme.colorScheme.secondary;
|
||||||
|
|
||||||
return OutlinedButton(
|
return OutlinedButton(
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
|
|||||||
@ -391,7 +391,7 @@ class _SearchableDropdownPanelState<T>
|
|||||||
? Icon(
|
? Icon(
|
||||||
Icons.check,
|
Icons.check,
|
||||||
size: 20,
|
size: 20,
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.secondary,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
selected: isSelected,
|
selected: isSelected,
|
||||||
@ -409,6 +409,9 @@ class _SearchableDropdownPanelState<T>
|
|||||||
onPressed: () => widget.onAddNew?.call(),
|
onPressed: () => widget.onAddNew?.call(),
|
||||||
icon: const Icon(Icons.add, size: 18),
|
icon: const Icon(Icons.add, size: 18),
|
||||||
label: Text(addLabel),
|
label: Text(addLabel),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: theme.colorScheme.primary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@ -72,7 +72,8 @@ class AppStatusChip extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final (color, label) = _resolveStatus(status);
|
final secondary = Theme.of(context).colorScheme.secondary;
|
||||||
|
final (color, label) = _resolveStatus(status, secondary);
|
||||||
if (forTable) {
|
if (forTable) {
|
||||||
return TableStatusBadge(
|
return TableStatusBadge(
|
||||||
label: label,
|
label: label,
|
||||||
@ -98,26 +99,26 @@ class AppStatusChip extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
(Color, String) _resolveStatus(String raw) {
|
(Color, String) _resolveStatus(String raw, Color secondary) {
|
||||||
final normalized = raw.trim().toLowerCase().replaceAll(' ', '_');
|
final normalized = raw.trim().toLowerCase().replaceAll(' ', '_');
|
||||||
switch (normalized) {
|
switch (normalized) {
|
||||||
case 'active':
|
case 'active':
|
||||||
return (Colors.green.shade700, EntityStatus.active.label);
|
return (secondary, EntityStatus.active.label);
|
||||||
case 'inactive':
|
case 'inactive':
|
||||||
return (Colors.grey.shade700, EntityStatus.inactive.label);
|
return (Colors.grey.shade700, EntityStatus.inactive.label);
|
||||||
case 'locked':
|
case 'locked':
|
||||||
return (Colors.orange.shade800, 'Locked');
|
return (Colors.orange.shade800, 'Locked');
|
||||||
case 'in_use':
|
case 'in_use':
|
||||||
return (const Color(0xFF16A34A), 'In Use');
|
return (secondary, 'In Use');
|
||||||
case 'under_maintenance':
|
case 'under_maintenance':
|
||||||
return (const Color(0xFFCA8A04), 'Under Maintenance');
|
return (const Color(0xFFCA8A04), 'Under Maintenance');
|
||||||
case 'available':
|
case 'available':
|
||||||
return (const Color(0xFF2563EB), 'Available');
|
return (secondary, 'Available');
|
||||||
case 'disposed':
|
case 'disposed':
|
||||||
case 'scrapped':
|
case 'scrapped':
|
||||||
return (Colors.grey.shade700, _humanizeStatus(raw));
|
return (Colors.grey.shade700, _humanizeStatus(raw));
|
||||||
default:
|
default:
|
||||||
return (Colors.blueGrey, _humanizeStatus(raw));
|
return (secondary, _humanizeStatus(raw));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -314,7 +314,7 @@ class _AttachmentRow extends StatelessWidget {
|
|||||||
Icon(
|
Icon(
|
||||||
_fileIcon(attachment),
|
_fileIcon(attachment),
|
||||||
size: 22,
|
size: 22,
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.secondary,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|||||||
@ -20,7 +20,7 @@ class KpiCard extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final cardColor = color ?? Theme.of(context).colorScheme.primary;
|
final cardColor = color ?? Theme.of(context).colorScheme.secondary;
|
||||||
|
|
||||||
return AppCard(
|
return AppCard(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
|
|||||||
@ -325,7 +325,7 @@ class _MasterInlineQuickAddFormState
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.add_circle_outline,
|
Icon(Icons.add_circle_outline,
|
||||||
size: 16, color: theme.colorScheme.primary),
|
size: 16, color: theme.colorScheme.secondary),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user