From 70064668a87a93fde637ebe6d5eb62d6c03c341a Mon Sep 17 00:00:00 2001 From: miakh <2659269+miakh@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:44:04 +0100 Subject: [PATCH] progress for import and fix for update users on import --- lib/dataModels/OccasionUserModel.dart | 2 +- .../AdministrationOccasion/UsersTab.dart | 4 - lib/services/DialogHelper.dart | 43 +++- lib/services/UserManagementHelper.dart | 215 +++++++++++------- 4 files changed, 166 insertions(+), 98 deletions(-) diff --git a/lib/dataModels/OccasionUserModel.dart b/lib/dataModels/OccasionUserModel.dart index dbf3b849..35eda887 100644 --- a/lib/dataModels/OccasionUserModel.dart +++ b/lib/dataModels/OccasionUserModel.dart @@ -57,7 +57,7 @@ class OccasionUserModel extends IPlutoRowModel { factory OccasionUserModel.fromImportedJson(Map json, [OccasionUserModel? original]) { return OccasionUserModel( occasion: RightsService.currentOccasion!, - user: json[Tb.occasion_users.user], + user: original?.user ?? json[Tb.occasion_users.user], role: json[Tb.occasion_users.role], data: { Tb.occasion_users.data_email: json[Tb.occasion_users.data_email], diff --git a/lib/pages/AdministrationOccasion/UsersTab.dart b/lib/pages/AdministrationOccasion/UsersTab.dart index 10e26e8f..fb8665fa 100644 --- a/lib/pages/AdministrationOccasion/UsersTab.dart +++ b/lib/pages/AdministrationOccasion/UsersTab.dart @@ -166,7 +166,6 @@ class _UsersTabState extends State { ); if (confirm) { - ValueNotifier invitedCount = ValueNotifier(0); Map retryAttempts = { for (var user in users) user: 0 }; @@ -177,8 +176,6 @@ class _UsersTabState extends State { try { // Send sign-in code and update progress await AuthService.sendSignInCode(user); - invitedCount.value++; - ToastHelper.Show( context, "Invited: {user}.".tr(namedArgs: { @@ -218,7 +215,6 @@ class _UsersTabState extends State { context, "Invite".tr(), users.length, - invitedCount, futures: inviteFutures, ); } diff --git a/lib/services/DialogHelper.dart b/lib/services/DialogHelper.dart index 48b6a0dc..081935bc 100644 --- a/lib/services/DialogHelper.dart +++ b/lib/services/DialogHelper.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:fstapp/dataModels/LanguageModel.dart'; import 'package:fstapp/dataModels/UserGroupInfoModel.dart'; import 'package:fstapp/dataModels/UserInfoModel.dart'; @@ -308,18 +310,14 @@ class DialogHelper{ static Future showProgressDialogAsync( BuildContext context, String title, - int total, - ValueNotifier progressNotifier, { + int total, { List>? futures, }) async { - if (futures != null) { - for (var future in futures) { - await future; - progressNotifier.value++; - } - } - - await showDialog( + // Completer to control the dialog lifecycle + final completer = Completer(); + var progressNotifier = ValueNotifier(0); + // Show the dialog immediately + showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { @@ -334,6 +332,16 @@ class DialogHelper{ Text("${"Progress".tr()}: $progress/$total"), SizedBox(height: 20), LinearProgressIndicator(value: total > 0 ? progress / total : 0), + if (progress >= total) ...[ + SizedBox(height: 20), + ElevatedButton( + onPressed: () { + Navigator.of(context).pop(); + completer.complete(); + }, + child: Text("Ok".tr()), + ), + ], ], ), ); @@ -341,5 +349,20 @@ class DialogHelper{ ); }, ); + + // Process the futures, updating progress + if (futures != null) { + for (var future in futures) { + await future; + progressNotifier.value++; + } + } + + // Wait for the user to press "OK" if not already completed + if (!completer.isCompleted) { + await completer.future; + } } + + } \ No newline at end of file diff --git a/lib/services/UserManagementHelper.dart b/lib/services/UserManagementHelper.dart index ba5d0262..06a218d9 100644 --- a/lib/services/UserManagementHelper.dart +++ b/lib/services/UserManagementHelper.dart @@ -4,7 +4,6 @@ import 'package:fstapp/dataServices/AuthService.dart'; import 'package:fstapp/dataServices/DbUsers.dart'; import 'package:fstapp/services/DialogHelper.dart'; import 'package:fstapp/services/ImportHelper.dart'; -import 'package:fstapp/services/NavigationService.dart'; import 'package:fstapp/services/ToastHelper.dart'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -13,103 +12,153 @@ import 'package:flutter/cupertino.dart'; class UserManagementHelper{ static Future import(BuildContext context) async { var file = await DialogHelper.dropFilesHere(context, "Import of users from CSV".tr(), "Ok".tr(), "Storno".tr()); - if(file==null) { - return; - } + if (file == null) return; + var users = await ImportHelper.getUsersFromFile(file); var addOrUpdateUsers = users.where((element) => element[Tb.occasion_users.data_text4]?.toLowerCase() != "storno"); var deleteUsers = users.where((element) => element[Tb.occasion_users.data_text4]?.toLowerCase() == "storno"); - var really = await DialogHelper.showConfirmationDialogAsync(context, - "Importing users".tr(), - "${"Users".tr()} (${users.length}):\n${users.map((value) => value[Tb.occasion_users.data_email]).toList().join(",\n")}", - confirmButtonMessage: "Proceed".tr()); + var proceed = await DialogHelper.showConfirmationDialogAsync( + context, + "Importing users".tr(), + "${"Users".tr()} (${users.length}):\n${users.map((value) => value[Tb.occasion_users.data_email]).toList().join(",\n")}", + confirmButtonMessage: "Proceed".tr(), + ); - if(!really) - { - return; - } + if (!proceed) return; var existingUsers = await DbUsers.getOccasionUsers(); + var toBeCreated = _getUsersToBeCreated(users, existingUsers); + var toBeUpdated = _getUsersToBeUpdated(users, existingUsers); + var toBeDeleted = _getUsersToBeDeleted(deleteUsers, addOrUpdateUsers, existingUsers); - List> toBeCreated = []; - List> toBeUpdated = []; - for(var u in users) - { - var existing = existingUsers.firstWhereOrNull((element) => element.data?[Tb.occasion_users.data_email]?.toLowerCase() == u[Tb.occasion_users.data_email]?.toLowerCase()); - if(existing == null) { - toBeCreated.add(u); - continue; - } - else if(existing.importedEquals(u)) { - continue; - } - else{ - u[Tb.occasion_users.user] = existing.user; - toBeUpdated.add(u); - } - } + await _handleCreateUsers(context, toBeCreated); + await _handleUpdateUsers(context, toBeUpdated, existingUsers); + await _handleDeleteUsers(context, toBeDeleted); + } - if(toBeCreated.isNotEmpty) { - var really = await DialogHelper.showConfirmationDialogAsync(context, - "Creating users".tr(), - "New users found. Do you want to create them?".tr() + +// Helper to get users to be created + static List> _getUsersToBeCreated( + Iterable> users, List existingUsers) { + return users.where((u) { + return existingUsers.firstWhereOrNull( + (e) => e.data?[Tb.occasion_users.data_email]?.toLowerCase() == + u[Tb.occasion_users.data_email]?.toLowerCase(), + ) == + null; + }).toList(); + } + +// Helper to get users to be updated + static List> _getUsersToBeUpdated( + Iterable> users, List existingUsers) { + return users.where((u) { + var existing = existingUsers.firstWhereOrNull( + (e) => e.data?[Tb.occasion_users.data_email]?.toLowerCase() == + u[Tb.occasion_users.data_email]?.toLowerCase(), + ); + return existing != null && !existing.importedEquals(u); + }).toList(); + } + +// Helper to get users to be deleted + static List _getUsersToBeDeleted( + Iterable> deleteUsers, + Iterable> addOrUpdateUsers, + List existingUsers) { + return deleteUsers.map((u) { + var existing = existingUsers.firstWhereOrNull( + (e) => e.data?[Tb.occasion_users.data_email] == u[Tb.occasion_users.data_email], + ); + var duplicated = addOrUpdateUsers.firstWhereOrNull( + (e) => e[Tb.occasion_users.data_email] == u[Tb.occasion_users.data_email], + ); + return (existing != null && duplicated == null) ? existing : null; + }).whereNotNull().toList(); + } + +// Handle creating users with progress dialog + static Future _handleCreateUsers(BuildContext context, List> toBeCreated) async { + if (toBeCreated.isEmpty) return; + + var proceed = await DialogHelper.showConfirmationDialogAsync( + context, + "Creating users".tr(), + "New users found. Do you want to create them?".tr() + "\n" + - "${"Users".tr()} (${toBeCreated.length}):\n${toBeCreated.map((value) => value[Tb.occasion_users.data_email]).toList().join(",\n")}", - confirmButtonMessage: "Proceed".tr()); - - if(really) { - toBeCreated.forEach((u) async { - await DbUsers.updateOccasionUser(OccasionUserModel.fromImportedJson(u)); - ToastHelper.Show(context, "Created {item}.".tr(namedArgs: {"item": u[Tb.occasion_users.data_email]})); - }); - } - } + "${"Users".tr()} (${toBeCreated.length}):\n${toBeCreated.map((u) => u[Tb.occasion_users.data_email]).join(",\n")}", + confirmButtonMessage: "Proceed".tr(), + ); - if(toBeUpdated.isNotEmpty) { - var really = await DialogHelper.showConfirmationDialogAsync(context, - "Updating users".tr(), - "These users have some changes. Do you want to update them?".tr() + + if (!proceed) return; + + await DialogHelper.showProgressDialogAsync( + context, + "Creating users".tr(), + toBeCreated.length, + futures: toBeCreated.map((u) async { + await DbUsers.updateOccasionUser(OccasionUserModel.fromImportedJson(u)); + ToastHelper.Show(context, "Created {item}.".tr(namedArgs: {"item": u[Tb.occasion_users.data_email]})); + }).toList(), + ); + } + +// Handle updating users with progress dialog + static Future _handleUpdateUsers( + BuildContext context, List> toBeUpdated, List existingUsers) async { + if (toBeUpdated.isEmpty) return; + + var proceed = await DialogHelper.showConfirmationDialogAsync( + context, + "Updating users".tr(), + "These users have some changes. Do you want to update them?".tr() + "\n" + - "${"Users".tr()} (${toBeUpdated.length}):\n${toBeUpdated.map((value) => value[Tb.occasion_users.data_email]).toList().join(",\n")}", - confirmButtonMessage: "Proceed".tr()); - - if(really) { - toBeUpdated.forEach((u) async { - await DbUsers.updateExistingImportedOccasionUser( - OccasionUserModel.fromImportedJson(u, - existingUsers.firstWhereOrNull((e)=>e.data?[Tb.occasion_users.data_email] == u[Tb.occasion_users.data_email]))); - ToastHelper.Show(context, "Updated {item}.".tr(namedArgs: {"item": u[Tb.occasion_users.data_email]})); - }); - } - } + "${"Users".tr()} (${toBeUpdated.length}):\n${toBeUpdated.map((u) => u[Tb.occasion_users.data_email]).join(",\n")}", + confirmButtonMessage: "Proceed".tr(), + ); - List toBeDeleted = []; - for(var u in deleteUsers) - { - var existing = existingUsers.firstWhereOrNull((element) => element.data?[Tb.occasion_users.data_email] == u[Tb.occasion_users.data_email]); - var duplicated = addOrUpdateUsers.firstWhereOrNull((element) => element[Tb.occasion_users.data_email] == u[Tb.occasion_users.data_email]); + if (!proceed) return; - if(existing != null && duplicated == null) { - toBeDeleted.add(existing); - } - } + await DialogHelper.showProgressDialogAsync( + context, + "Updating users".tr(), + toBeUpdated.length, + futures: toBeUpdated.map((u) async { + var existing = existingUsers.firstWhere( + (e) => e.data?[Tb.occasion_users.data_email] == u[Tb.occasion_users.data_email], + ); + var fromExisting = OccasionUserModel.fromImportedJson(u, existing); + await DbUsers.updateExistingImportedOccasionUser(fromExisting); + ToastHelper.Show(context, "Updated {item}.".tr(namedArgs: {"item": u[Tb.occasion_users.data_email]})); + }).toList(), + ); + } - if(toBeDeleted.isNotEmpty) { - var reallyDelete = await DialogHelper.showConfirmationDialogAsync(context, - "Removing users", - "These users have been removed, but they still exist in the application. Do you want to remove them?".tr() + - "\n" + - "${"Users".tr()} (${toBeDeleted.length}):\n${toBeDeleted.map((value) => value.toBasicString()).toList().join(",\n")}", - confirmButtonMessage: "Proceed".tr()); - - if(reallyDelete) { - toBeDeleted.forEach((existing) async { - await DbUsers.deleteUser(existing.user!, existing.occasion!); - ToastHelper.Show(context, "Removed {item}.".tr(namedArgs: {"item": existing.toBasicString()})); - }); - } - } +// Handle deleting users with progress dialog + static Future _handleDeleteUsers(BuildContext context, List toBeDeleted) async { + if (toBeDeleted.isEmpty) return; + + var proceed = await DialogHelper.showConfirmationDialogAsync( + context, + "Removing users".tr(), + "These users have been removed, but they still exist in the application. Do you want to remove them?".tr() + + "\n" + + "${"Users".tr()} (${toBeDeleted.length}):\n${toBeDeleted.map((u) => u.toBasicString()).join(",\n")}", + confirmButtonMessage: "Proceed".tr(), + ); + + if (!proceed) return; + + + await DialogHelper.showProgressDialogAsync( + context, + "Removing users".tr(), + toBeDeleted.length, + futures: toBeDeleted.map((existing) async { + await DbUsers.deleteUser(existing.user!, existing.occasion!); + ToastHelper.Show(context, "Removed {item}.".tr(namedArgs: {"item": existing.toBasicString()})); + }).toList(), + ); } static Future unsafeChangeUserPassword(BuildContext context, OccasionUserModel user) async {