199 lines
7.4 KiB
Dart
199 lines
7.4 KiB
Dart
import '../../../../shared/widgets/app_card.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import '../../../../core/utils/formatters.dart';
|
|
|
|
import '../../../../core/errors/failure.dart';
|
|
import '../../../../shared/models/user_management_models.dart';
|
|
import '../../../../shared/widgets/app_button.dart';
|
|
import '../../../../shared/widgets/app_confirmation_dialog.dart';
|
|
import '../../../../shared/widgets/app_loading_view.dart';
|
|
import '../../../../shared/widgets/app_status_chip.dart';
|
|
import '../../../../shared/widgets/detail_overview_widgets.dart';
|
|
import '../../../../shared/widgets/error_view.dart';
|
|
import '../../../../shared/widgets/page_header.dart';
|
|
import '../providers/users_provider.dart';
|
|
import '../../../../shared/widgets/app_toast.dart';
|
|
|
|
class UserDetailScreen extends ConsumerStatefulWidget {
|
|
const UserDetailScreen({super.key, required this.userId});
|
|
|
|
final String userId;
|
|
|
|
@override
|
|
ConsumerState<UserDetailScreen> createState() => _UserDetailScreenState();
|
|
}
|
|
|
|
class _UserDetailScreenState extends ConsumerState<UserDetailScreen> {
|
|
bool _requestedFreshLoad = false;
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
if (_requestedFreshLoad) return;
|
|
_requestedFreshLoad = true;
|
|
// Always hit GET user-by-id when opening view.
|
|
ref.invalidate(userDetailProvider(widget.userId));
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(covariant UserDetailScreen oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
if (oldWidget.userId != widget.userId) {
|
|
ref.invalidate(userDetailProvider(widget.userId));
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final userId = widget.userId;
|
|
final userAsync = ref.watch(userDetailProvider(userId));
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: userAsync.when(
|
|
loading: () => const AppLoadingView(message: 'Loading user details...'),
|
|
error: (error, _) => ErrorView.fromFailure(
|
|
error is Failure ? error : Failure.unknown(message: error.toString()),
|
|
onRetry: () => ref.invalidate(userDetailProvider(userId)),
|
|
),
|
|
data: (user) => Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
PageHeader(
|
|
title: user.fullName,
|
|
subtitle: user.employeeCode,
|
|
actions: [
|
|
OutlinedButton.icon(
|
|
onPressed: () => context.push('/users/$userId/edit'),
|
|
icon: const Icon(Icons.edit_outlined),
|
|
label: const Text('Edit'),
|
|
),
|
|
const SizedBox(width: 8),
|
|
AppButton(
|
|
label: 'Deactivate',
|
|
expand: false,
|
|
onPressed: () => _deactivate(context),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 24),
|
|
AppCard(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: DetailOverviewCard(
|
|
title: 'User Details',
|
|
children: [
|
|
DetailOverviewSection(
|
|
title: 'Summary',
|
|
child: DetailSummaryStrip(
|
|
metrics: [
|
|
DetailSummaryMetric(
|
|
icon: Icons.flag_outlined,
|
|
label: 'Status',
|
|
accent: Theme.of(context).colorScheme.secondary,
|
|
child: AppStatusChip(
|
|
status: user.status,
|
|
compact: true,
|
|
),
|
|
),
|
|
DetailSummaryMetric(
|
|
icon: Icons.badge_outlined,
|
|
label: 'Employee Code',
|
|
child: Text(
|
|
user.employeeCode,
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.titleMedium
|
|
?.copyWith(fontWeight: FontWeight.w700),
|
|
),
|
|
),
|
|
DetailSummaryMetric(
|
|
icon: Icons.admin_panel_settings_outlined,
|
|
label: 'Role',
|
|
child: Text(
|
|
user.roleLabel,
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.titleMedium
|
|
?.copyWith(fontWeight: FontWeight.w700),
|
|
),
|
|
),
|
|
DetailSummaryMetric(
|
|
icon: Icons.apartment_outlined,
|
|
label: 'Department',
|
|
child: Text(
|
|
user.departmentLabel,
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.titleMedium
|
|
?.copyWith(fontWeight: FontWeight.w700),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
DetailOverviewSection(
|
|
title: 'Contact',
|
|
child: DetailInfoGrid(
|
|
items: [
|
|
DetailInfoItem('Email', user.email),
|
|
DetailInfoItem('Mobile', user.mobile),
|
|
],
|
|
),
|
|
),
|
|
DetailOverviewSection(
|
|
title: 'Account',
|
|
showDivider: false,
|
|
child: DetailInfoGrid(
|
|
items: [
|
|
DetailInfoItem('Role', user.roleLabel),
|
|
DetailInfoItem('Department', user.departmentLabel),
|
|
if (user.createdAt != null)
|
|
DetailInfoItem(
|
|
'Created',
|
|
DateFormatter.displayDateTime(user.createdAt),
|
|
),
|
|
if (user.updatedAt != null)
|
|
DetailInfoItem(
|
|
'Updated',
|
|
DateFormatter.displayDateTime(user.updatedAt),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _deactivate(BuildContext context) async {
|
|
final confirmed = await showAppConfirmationDialog(
|
|
context: context,
|
|
title: 'Deactivate user',
|
|
message: 'Are you sure you want to deactivate this user?',
|
|
confirmLabel: 'Deactivate',
|
|
isDestructive: true,
|
|
);
|
|
if (confirmed != true || !context.mounted) return;
|
|
|
|
final success =
|
|
await ref.read(userDetailProvider(widget.userId).notifier).deactivate();
|
|
if (!context.mounted) return;
|
|
|
|
showAppToastFromSnackBar(
|
|
context,
|
|
SnackBar(
|
|
content: Text(success ? 'User deactivated' : 'Failed to deactivate'),
|
|
),
|
|
);
|
|
if (success) context.pop();
|
|
}
|
|
}
|