A better state and alerts

This commit is contained in:
2026-02-23 20:19:07 +08:00
parent 0b900d3480
commit 1074572905
17 changed files with 673 additions and 428 deletions
+51 -30
View File
@@ -9,6 +9,7 @@ import '../../widgets/mono_text.dart';
import '../../widgets/responsive_body.dart';
import '../../theme/app_surfaces.dart';
import '../../widgets/tasq_adaptive_list.dart';
import '../../utils/snackbar.dart';
class OfficesScreen extends ConsumerStatefulWidget {
const OfficesScreen({super.key});
@@ -170,6 +171,7 @@ class _OfficesScreenState extends ConsumerState<OfficesScreen> {
context: context,
builder: (dialogContext) {
final servicesAsync = ref.watch(servicesOnceProvider);
bool saving = false;
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
@@ -184,6 +186,7 @@ class _OfficesScreenState extends ConsumerState<OfficesScreen> {
decoration: const InputDecoration(
labelText: 'Office name',
),
enabled: !saving,
),
const SizedBox(height: 12),
servicesAsync.when(
@@ -205,8 +208,9 @@ class _OfficesScreenState extends ConsumerState<OfficesScreen> {
),
),
],
onChanged: (v) =>
setState(() => selectedServiceId = v),
onChanged: saving
? null
: (v) => setState(() => selectedServiceId = v),
);
},
loading: () => const Padding(
@@ -220,37 +224,54 @@ class _OfficesScreenState extends ConsumerState<OfficesScreen> {
),
actions: [
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(),
onPressed: saving
? null
: () => Navigator.of(dialogContext).pop(),
child: const Text('Cancel'),
),
FilledButton(
onPressed: () async {
final name = nameController.text.trim();
if (name.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Name is required.')),
);
return;
}
final controller = ref.read(officesControllerProvider);
if (office == null) {
await controller.createOffice(
name: name,
serviceId: selectedServiceId,
);
} else {
await controller.updateOffice(
id: office.id,
name: name,
serviceId: selectedServiceId,
);
}
ref.invalidate(officesProvider);
if (context.mounted) {
Navigator.of(dialogContext).pop();
}
},
child: Text(office == null ? 'Create' : 'Save'),
onPressed: saving
? null
: () async {
final name = nameController.text.trim();
if (name.isEmpty) {
showWarningSnackBar(context, 'Name is required.');
return;
}
setState(() => saving = true);
final controller = ref.read(
officesControllerProvider,
);
if (office == null) {
await controller.createOffice(
name: name,
serviceId: selectedServiceId,
);
} else {
await controller.updateOffice(
id: office.id,
name: name,
serviceId: selectedServiceId,
);
}
ref.invalidate(officesProvider);
if (context.mounted) {
Navigator.of(dialogContext).pop();
showSuccessSnackBar(
context,
office == null
? 'Office "$name" has been created successfully.'
: 'Office "$name" has been updated successfully.',
);
}
},
child: saving
? const SizedBox(
height: 18,
width: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: Text(office == null ? 'Create' : 'Save'),
),
],
);
+15 -38
View File
@@ -16,6 +16,7 @@ import '../../utils/app_time.dart';
import '../../widgets/mono_text.dart';
import '../../widgets/responsive_body.dart';
import '../../widgets/tasq_adaptive_list.dart';
import '../../utils/snackbar.dart';
class UserManagementScreen extends ConsumerStatefulWidget {
const UserManagementScreen({super.key});
@@ -501,16 +502,12 @@ class _UserManagementScreenState extends ConsumerState<UserManagementScreen> {
final role = _selectedRole ?? profile.role;
final fullName = _fullNameController.text.trim();
if (fullName.isEmpty) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('Full name is required.')));
showWarningSnackBar(context, 'Full name is required.');
return false;
}
if (_selectedOfficeIds.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Select at least one office.')),
);
showWarningSnackBar(context, 'Select at least one office.');
return false;
}
@@ -542,15 +539,11 @@ class _UserManagementScreenState extends ConsumerState<UserManagementScreen> {
ref.invalidate(userOfficesProvider);
if (!context.mounted) return true;
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('User updated.')));
showSuccessSnackBar(context, 'User "$fullName" updated successfully.');
return true;
} catch (error) {
if (!context.mounted) return false;
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Update failed: $error')));
showErrorSnackBar(context, 'Update failed: $error');
return false;
} finally {
setDialogState(() => _isSaving = false);
@@ -598,9 +591,7 @@ class _UserManagementScreenState extends ConsumerState<UserManagementScreen> {
if (!dialogContext.mounted) return;
Navigator.of(dialogContext).pop();
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Password updated.')),
);
showSuccessSnackBar(context, 'Password updated.');
} catch (error) {
final msg = error.toString();
if (msg.contains('Unauthorized') ||
@@ -608,20 +599,15 @@ class _UserManagementScreenState extends ConsumerState<UserManagementScreen> {
msg.contains('expired')) {
await ref.read(authControllerProvider).signOut();
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Session expired — please sign in again.',
),
),
showInfoSnackBar(
context,
'Session expired — please sign in again.',
);
return;
}
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Reset failed: $error')),
);
showErrorSnackBar(context, 'Reset failed: $error');
}
},
child: const Text('Update password'),
@@ -645,12 +631,9 @@ class _UserManagementScreenState extends ConsumerState<UserManagementScreen> {
ref.invalidate(currentProfileProvider);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
locked ? 'User locked (app-level).' : 'User unlocked (app-level).',
),
),
showInfoSnackBar(
context,
locked ? 'User locked (app-level).' : 'User unlocked (app-level).',
);
} catch (error) {
final msg = error.toString();
@@ -659,18 +642,12 @@ class _UserManagementScreenState extends ConsumerState<UserManagementScreen> {
msg.contains('expired')) {
await ref.read(authControllerProvider).signOut();
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Session expired — please sign in again.'),
),
);
showInfoSnackBar(context, 'Session expired — please sign in again.');
return;
}
if (!mounted) return;
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Lock update failed: $error')));
showErrorSnackBar(context, 'Lock update failed: $error');
} finally {
if (mounted) {
setState(() => _isSaving = false);