chore: remove unused profile lock migration and admin_profile_service
This commit is contained in:
@@ -10,6 +10,7 @@ import '../../providers/profile_provider.dart';
|
||||
import '../../providers/tickets_provider.dart';
|
||||
import '../../theme/app_surfaces.dart';
|
||||
import '../../providers/user_offices_provider.dart';
|
||||
|
||||
import '../../utils/app_time.dart';
|
||||
import '../../widgets/mono_text.dart';
|
||||
import '../../widgets/responsive_body.dart';
|
||||
@@ -36,14 +37,8 @@ class _UserManagementScreenState extends ConsumerState<UserManagementScreen> {
|
||||
|
||||
String? _selectedUserId;
|
||||
String? _selectedRole;
|
||||
Set<String> _selectedOfficeIds = {};
|
||||
AdminUserStatus? _selectedStatus;
|
||||
final Set<String> _selectedOfficeIds = {};
|
||||
bool _isSaving = false;
|
||||
bool _isStatusLoading = false;
|
||||
final Map<String, AdminUserStatus> _statusCache = {};
|
||||
final Set<String> _statusLoading = {};
|
||||
final Set<String> _statusErrors = {};
|
||||
Set<String> _prefetchedUserIds = {};
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -106,8 +101,6 @@ class _UserManagementScreenState extends ConsumerState<UserManagementScreen> {
|
||||
final assignments = assignmentsAsync.valueOrNull ?? [];
|
||||
final messages = messagesAsync.valueOrNull ?? [];
|
||||
|
||||
_prefetchStatuses(profiles);
|
||||
|
||||
final lastActiveByUser = <String, DateTime>{};
|
||||
for (final message in messages) {
|
||||
final senderId = message.senderId;
|
||||
@@ -168,12 +161,12 @@ class _UserManagementScreenState extends ConsumerState<UserManagementScreen> {
|
||||
TasQColumn<Profile>(
|
||||
header: 'Email',
|
||||
cellBuilder: (context, profile) {
|
||||
final status = _statusCache[profile.id];
|
||||
final hasError = _statusErrors.contains(profile.id);
|
||||
final email = hasError
|
||||
? 'Unavailable'
|
||||
: (status?.email ?? 'Unknown');
|
||||
return Text(email);
|
||||
final statusAsync = ref.watch(adminUserStatusProvider(profile.id));
|
||||
return statusAsync.when(
|
||||
data: (s) => Text(s.email ?? 'Unknown'),
|
||||
loading: () => const Text('Loading...'),
|
||||
error: (_, __) => const Text('Unknown'),
|
||||
);
|
||||
},
|
||||
),
|
||||
TasQColumn<Profile>(
|
||||
@@ -190,11 +183,15 @@ class _UserManagementScreenState extends ConsumerState<UserManagementScreen> {
|
||||
TasQColumn<Profile>(
|
||||
header: 'Status',
|
||||
cellBuilder: (context, profile) {
|
||||
final status = _statusCache[profile.id];
|
||||
final hasError = _statusErrors.contains(profile.id);
|
||||
final isLoading = _statusLoading.contains(profile.id);
|
||||
final statusLabel = _userStatusLabel(status, hasError, isLoading);
|
||||
return _StatusBadge(label: statusLabel);
|
||||
final statusAsync = ref.watch(adminUserStatusProvider(profile.id));
|
||||
return statusAsync.when(
|
||||
data: (s) {
|
||||
final statusLabel = s.isLocked ? 'Locked' : 'Active';
|
||||
return _StatusBadge(label: statusLabel);
|
||||
},
|
||||
loading: () => _StatusBadge(label: 'Loading'),
|
||||
error: (_, __) => _StatusBadge(label: 'Unknown'),
|
||||
);
|
||||
},
|
||||
),
|
||||
TasQColumn<Profile>(
|
||||
@@ -209,13 +206,9 @@ class _UserManagementScreenState extends ConsumerState<UserManagementScreen> {
|
||||
_showUserDialog(context, profile, offices, assignments),
|
||||
mobileTileBuilder: (context, profile, actions) {
|
||||
final label = profile.fullName.isEmpty ? profile.id : profile.fullName;
|
||||
final status = _statusCache[profile.id];
|
||||
final hasError = _statusErrors.contains(profile.id);
|
||||
final isLoading = _statusLoading.contains(profile.id);
|
||||
final email = hasError ? 'Unavailable' : (status?.email ?? 'Unknown');
|
||||
final statusAsync = ref.watch(adminUserStatusProvider(profile.id));
|
||||
final officesAssigned = officeCountByUser[profile.id] ?? 0;
|
||||
final lastActive = lastActiveByUser[profile.id];
|
||||
final statusLabel = _userStatusLabel(status, hasError, isLoading);
|
||||
|
||||
return Card(
|
||||
child: ListTile(
|
||||
@@ -231,10 +224,19 @@ class _UserManagementScreenState extends ConsumerState<UserManagementScreen> {
|
||||
Text('Last active: ${_formatLastActiveLabel(lastActive)}'),
|
||||
const SizedBox(height: 4),
|
||||
MonoText('ID ${profile.id}'),
|
||||
Text('Email: $email'),
|
||||
statusAsync.when(
|
||||
data: (s) => Text('Email: ${s.email ?? 'Unknown'}'),
|
||||
loading: () => const Text('Email: Loading...'),
|
||||
error: (_, __) => const Text('Email: Unknown'),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: _StatusBadge(label: statusLabel),
|
||||
trailing: statusAsync.when(
|
||||
data: (s) =>
|
||||
_StatusBadge(label: s.isLocked ? 'Locked' : 'Active'),
|
||||
loading: () => _StatusBadge(label: 'Loading'),
|
||||
error: (_, __) => _StatusBadge(label: 'Unknown'),
|
||||
),
|
||||
onTap: () =>
|
||||
_showUserDialog(context, profile, offices, assignments),
|
||||
),
|
||||
@@ -279,7 +281,6 @@ class _UserManagementScreenState extends ConsumerState<UserManagementScreen> {
|
||||
List<Office> offices,
|
||||
List<UserOffice> assignments,
|
||||
) async {
|
||||
await _selectUser(profile);
|
||||
final currentOfficeIds = assignments
|
||||
.where((assignment) => assignment.userId == profile.id)
|
||||
.map((assignment) => assignment.officeId)
|
||||
@@ -319,44 +320,6 @@ class _UserManagementScreenState extends ConsumerState<UserManagementScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
void _ensureStatusLoaded(String userId) {
|
||||
if (_statusCache.containsKey(userId) || _statusLoading.contains(userId)) {
|
||||
return;
|
||||
}
|
||||
_statusLoading.add(userId);
|
||||
_statusErrors.remove(userId);
|
||||
ref
|
||||
.read(adminUserControllerProvider)
|
||||
.fetchStatus(userId)
|
||||
.then((status) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_statusCache[userId] = status;
|
||||
_statusLoading.remove(userId);
|
||||
});
|
||||
})
|
||||
.catchError((_) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_statusLoading.remove(userId);
|
||||
_statusErrors.add(userId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _prefetchStatuses(List<Profile> profiles) {
|
||||
final ids = profiles.map((profile) => profile.id).toSet();
|
||||
final missing = ids.difference(_prefetchedUserIds);
|
||||
if (missing.isEmpty) return;
|
||||
_prefetchedUserIds = ids;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
for (final userId in missing) {
|
||||
_ensureStatusLoaded(userId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildUserForm(
|
||||
BuildContext context,
|
||||
Profile profile,
|
||||
@@ -383,7 +346,67 @@ class _UserManagementScreenState extends ConsumerState<UserManagementScreen> {
|
||||
decoration: const InputDecoration(labelText: 'Role'),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildStatusRow(profile),
|
||||
|
||||
// Email and lock status are retrieved from auth via Edge Function / admin API.
|
||||
Consumer(
|
||||
builder: (context, ref, _) {
|
||||
final statusAsync = ref.watch(adminUserStatusProvider(profile.id));
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
statusAsync.when(
|
||||
data: (s) => Text(
|
||||
'Email: ${s.email ?? 'Unknown'}',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
loading: () => Text(
|
||||
'Email: Loading...',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
error: (_, __) => Text(
|
||||
'Email: Unknown',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
onPressed: _isSaving
|
||||
? null
|
||||
: () => _showPasswordResetDialog(profile.id),
|
||||
icon: const Icon(Icons.password),
|
||||
label: const Text('Reset password'),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
statusAsync.when(
|
||||
data: (s) => OutlinedButton.icon(
|
||||
onPressed: _isSaving
|
||||
? null
|
||||
: () => _toggleLock(profile.id, !s.isLocked),
|
||||
icon: Icon(s.isLocked ? Icons.lock_open : Icons.lock),
|
||||
label: Text(s.isLocked ? 'Unlock' : 'Lock'),
|
||||
),
|
||||
loading: () => OutlinedButton.icon(
|
||||
onPressed: null,
|
||||
icon: const Icon(Icons.lock),
|
||||
label: const Text('Loading...'),
|
||||
),
|
||||
error: (_, __) => OutlinedButton.icon(
|
||||
onPressed: _isSaving
|
||||
? null
|
||||
: () => _toggleLock(profile.id, true),
|
||||
icon: const Icon(Icons.lock),
|
||||
label: const Text('Lock'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
Text('Offices', style: Theme.of(context).textTheme.titleSmall),
|
||||
const SizedBox(height: 8),
|
||||
@@ -445,82 +468,6 @@ class _UserManagementScreenState extends ConsumerState<UserManagementScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusRow(Profile profile) {
|
||||
final email = _selectedStatus?.email;
|
||||
final isLocked = _selectedStatus?.isLocked ?? false;
|
||||
final lockLabel = isLocked ? 'Unlock' : 'Lock';
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Email: ${email ?? 'Loading...'}',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
onPressed: _isStatusLoading
|
||||
? null
|
||||
: () => _showPasswordResetDialog(profile.id),
|
||||
icon: const Icon(Icons.password),
|
||||
label: const Text('Reset password'),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _isStatusLoading
|
||||
? null
|
||||
: () => _toggleLock(profile.id, !isLocked),
|
||||
icon: Icon(isLocked ? Icons.lock_open : Icons.lock),
|
||||
label: Text(lockLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _selectUser(Profile profile) async {
|
||||
setState(() {
|
||||
_selectedUserId = profile.id;
|
||||
_selectedRole = profile.role;
|
||||
_fullNameController.text = profile.fullName;
|
||||
_selectedStatus = null;
|
||||
_isStatusLoading = true;
|
||||
});
|
||||
|
||||
final assignments = ref.read(userOfficesProvider).valueOrNull ?? [];
|
||||
final officeIds = assignments
|
||||
.where((assignment) => assignment.userId == profile.id)
|
||||
.map((assignment) => assignment.officeId)
|
||||
.toSet();
|
||||
setState(() => _selectedOfficeIds = officeIds);
|
||||
|
||||
try {
|
||||
final status = await ref
|
||||
.read(adminUserControllerProvider)
|
||||
.fetchStatus(profile.id);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_selectedStatus = status;
|
||||
_statusCache[profile.id] = status;
|
||||
});
|
||||
}
|
||||
} catch (_) {
|
||||
if (mounted) {
|
||||
setState(
|
||||
() =>
|
||||
_selectedStatus = AdminUserStatus(email: null, bannedUntil: null),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => _isStatusLoading = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _saveChanges(
|
||||
BuildContext context,
|
||||
Profile profile,
|
||||
@@ -646,18 +593,24 @@ class _UserManagementScreenState extends ConsumerState<UserManagementScreen> {
|
||||
}
|
||||
|
||||
Future<void> _toggleLock(String userId, bool locked) async {
|
||||
setState(() => _isStatusLoading = true);
|
||||
setState(() => _isSaving = true);
|
||||
try {
|
||||
// Use AdminUserController (Edge Function or direct DB) to lock/unlock.
|
||||
await ref
|
||||
.read(adminUserControllerProvider)
|
||||
.setLock(userId: userId, locked: locked);
|
||||
final status = await ref
|
||||
.read(adminUserControllerProvider)
|
||||
.fetchStatus(userId);
|
||||
|
||||
// Refresh profile streams so other UI updates observe the change.
|
||||
ref.invalidate(profilesProvider);
|
||||
ref.invalidate(currentProfileProvider);
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() => _selectedStatus = status);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(locked ? 'User locked.' : 'User unlocked.')),
|
||||
SnackBar(
|
||||
content: Text(
|
||||
locked ? 'User locked (app-level).' : 'User unlocked (app-level).',
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
if (!mounted) return;
|
||||
@@ -666,23 +619,12 @@ class _UserManagementScreenState extends ConsumerState<UserManagementScreen> {
|
||||
).showSnackBar(SnackBar(content: Text('Lock update failed: $error')));
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => _isStatusLoading = false);
|
||||
setState(() => _isSaving = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _userStatusLabel(
|
||||
AdminUserStatus? status,
|
||||
bool hasError,
|
||||
bool isLoading,
|
||||
) {
|
||||
if (isLoading) return 'Loading';
|
||||
if (hasError) return 'Status error';
|
||||
if (status == null) return 'Unknown';
|
||||
return status.isLocked ? 'Locked' : 'Active';
|
||||
}
|
||||
|
||||
String _formatLastActiveLabel(DateTime? value) {
|
||||
if (value == null) return 'N/A';
|
||||
final now = AppTime.now();
|
||||
|
||||
Reference in New Issue
Block a user