194 lines
6.1 KiB
Dart
194 lines
6.1 KiB
Dart
import '../../../../shared/widgets/app_card.dart';
|
|
import '../../../../shared/widgets/app_data_table.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../../../../core/errors/failure.dart';
|
|
import '../../../../core/utils/responsive_utils.dart';
|
|
import '../../../../shared/models/permission_matrix_models.dart';
|
|
import '../../../../shared/widgets/app_button.dart';
|
|
import '../../../../shared/widgets/app_loading_view.dart';
|
|
import '../../../../shared/widgets/error_view.dart';
|
|
import '../../../../shared/widgets/page_header.dart';
|
|
import '../providers/roles_provider.dart';
|
|
import '../../../../shared/widgets/app_toast.dart';
|
|
|
|
class PermissionMatrixScreen extends ConsumerStatefulWidget {
|
|
const PermissionMatrixScreen({super.key, required this.roleId});
|
|
|
|
final String roleId;
|
|
|
|
@override
|
|
ConsumerState<PermissionMatrixScreen> createState() => _PermissionMatrixScreenState();
|
|
}
|
|
|
|
class _PermissionMatrixScreenState extends ConsumerState<PermissionMatrixScreen> {
|
|
bool _isSaving = false;
|
|
|
|
Future<void> _save() async {
|
|
setState(() => _isSaving = true);
|
|
final success =
|
|
await ref.read(permissionMatrixProvider(widget.roleId).notifier).save();
|
|
if (!mounted) return;
|
|
setState(() => _isSaving = false);
|
|
showAppToastFromSnackBar(context,
|
|
SnackBar(
|
|
content: Text(success ? 'Permissions saved' : 'Failed to save permissions'),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final matrixAsync = ref.watch(permissionMatrixProvider(widget.roleId));
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: matrixAsync.when(
|
|
loading: () => const AppLoadingView(message: 'Loading permission matrix...'),
|
|
error: (error, _) => ErrorView.fromFailure(
|
|
error is Failure ? error : Failure.unknown(message: error.toString()),
|
|
onRetry: () => ref.invalidate(permissionMatrixProvider(widget.roleId)),
|
|
),
|
|
data: (matrix) => Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
PageHeader(
|
|
title: 'Permission Matrix',
|
|
subtitle: matrix.roleName,
|
|
actions: [
|
|
AppButton(
|
|
label: 'Save Changes',
|
|
expand: false,
|
|
isLoading: _isSaving,
|
|
onPressed: _save,
|
|
),
|
|
],
|
|
),
|
|
Expanded(
|
|
child: context.isMobile
|
|
? _MatrixCardList(roleId: widget.roleId, matrix: matrix)
|
|
: _MatrixGrid(roleId: widget.roleId, matrix: matrix),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _MatrixGrid extends ConsumerWidget {
|
|
const _MatrixGrid({required this.roleId, required this.matrix});
|
|
|
|
final String roleId;
|
|
final RolePermissionMatrix matrix;
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
return AppDataTable<PermissionMatrixModuleRow>(
|
|
wrapInCard: true,
|
|
columns: [
|
|
AppDataColumn(
|
|
id: 'module',
|
|
label: 'Module',
|
|
sortKey: 'module',
|
|
flex: 3,
|
|
enableSearch: false,
|
|
searchText: (row) => row.name,
|
|
cellBuilder: (_, row) => AppTableCell.text(row.name),
|
|
),
|
|
...matrix.actionColumns.map(
|
|
(action) => AppDataColumn(
|
|
label: permissionActionLabel(action),
|
|
flex: 1,
|
|
alignment: Alignment.center,
|
|
enableSearch: false,
|
|
cellBuilder: (_, row) {
|
|
final applicable = isPermissionActionApplicable(
|
|
row.code,
|
|
action,
|
|
columnActions: matrix.actionColumns,
|
|
);
|
|
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,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
rows: matrix.modules,
|
|
);
|
|
}
|
|
}
|
|
|
|
class _MatrixCardList extends ConsumerWidget {
|
|
const _MatrixCardList({required this.roleId, required this.matrix});
|
|
|
|
final String roleId;
|
|
final RolePermissionMatrix matrix;
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
return ListView.separated(
|
|
itemCount: matrix.modules.length,
|
|
separatorBuilder: (_, __) => const SizedBox(height: 12),
|
|
itemBuilder: (context, index) {
|
|
final row = matrix.modules[index];
|
|
return AppCard(
|
|
child: ExpansionTile(
|
|
title: Text(row.name),
|
|
children: matrix.actionColumns
|
|
.map(
|
|
(action) {
|
|
final applicable = isPermissionActionApplicable(
|
|
row.code,
|
|
action,
|
|
columnActions: matrix.actionColumns,
|
|
);
|
|
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(),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class _PermissionSwitch extends StatelessWidget {
|
|
const _PermissionSwitch({
|
|
required this.label,
|
|
required this.value,
|
|
required this.onChanged,
|
|
});
|
|
|
|
final String label;
|
|
final bool value;
|
|
final ValueChanged<bool>? onChanged;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return SwitchListTile(
|
|
title: Text(label),
|
|
value: value,
|
|
onChanged: onChanged,
|
|
);
|
|
}
|
|
}
|