-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Admin UI: Overview of all identities (#593)
* feat: add data table 2 * Admin UI: Login page (#584) * chore: adapt the header appearance * chore: add new design for login box * chore: adapt login position in padding widget via screen size * chore: logic for displaying the error message for attempted login * chore: remove error message * chore: add error message * chore: change the button colors and add a text button * chore: remove Visibility widget * chore: add text style for text button * chore: change todo comment according to the flutter style * refactor: extract the app title to be a separate widget * refactor: extract text field to be reusable * chore: use new app title widget * chore: use custom text field * chore: make the text field fixed height * chore: rename variables and make them private * chore: update imports * chore: use extracted app title widget * chore: remove unnecessary widgets and center the card * chore: add custom colors and move login button to the bottom * chore: remove unused import * refactor: rename folder and files appropriately * refactor: extract sized box into a separate custom widget * refactor: extract elevated button into a separate custom widget * chore: make the variable private * chore: local variable should not start with underscore * chore: add DI for baseUrl * fix: untangle coding * fix: imports * fix: make CustomColors easier accessible * fix: make prettier * feat: add gaps * fix: rename file * refactor: remove CustomX, make whole logic with one variable * fix: bool logic * ci: add jkoenig134 as codeowner * chore: remove accidentally committet code * fix: undo change * refactor: simplify login screen * refactor: simplify app title * refactor: PR comments * chore: inline svg picture * chore: make multi line params last --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Julian König <[email protected]> Co-authored-by: Timo Notheisen <[email protected]> * feat: add multiselect * feat: add data table 2 and multi dropdown * fix: remove dependencies from wrong pubspec * fix: remove filter when there are no filters applied * chore: add builders * chore: display the IdentityOverview * fix: change the widget * feat: add identity overview * feat: add components * feat: add shared filter fields * chore: tier overview (wip) * chore: identity details (wip) * chore: pagination (wip) * chore: update rowCount * fix: pagination * fix: pagination * chore: remove comment and print * chore: remove comments * fix: revert created files * chore: use switch expression * chore: use gaps 16 and enlarge the textfield to 120 * chore: remove unused controller * chore: use async paginated data table 2 * chore: rename variables and add this.* * chore: change the named parameter * chore: change the color depending of a theme mode * chore: rename a label * chore: remove comment * chore: move files into a new folder * chore: remove components.dart and change the import * chore: move the file in the new folder and implement async data table source * chore: add trailing commas * refactor: change code structure / remove empty widget * chore: add logger * fix: data loading * chore: make selected date nullable and remove isDateSelected * chore: make variables private and remove late * fix: overhaul sorting and filtering * fix: add empty hint * fix: remove dynamic * chore: add set state * chore: remove onDateSelected from setState * chore: pageNumber should start from 0 * chore: trigger order by from odata request * chore: remove required * chore: make constructor inline * chore: remove unnecessary setState * chore: rename the method * chore: add onOptionRemoved and make the dropdown wider * fix: revert remove the onOptionRemoved * fix: remove comments * fix: use map instead of List<_Operator> * fix: remove unused scrollController * feat: add intl dependency * chore: add optional and remove unnecessary variables and method * chore: use absent method * fix: change substring to date format * fix: use lastLoginAt * fix: add a statement to check whether the lastLoginAt is null or not * refactor: simplify * chore: remove unused field * chore: remove the unused from method * fix: make method inline * fix: change to named parameter * fix: make field private * fix: undo the change * fix: make method inline * fix: use record * fix: rename variables * fix: use setter * fix: resolve ! for newValue * fix: move callback outside of setState * fix: rename a method * fix: maximum date that can be picked is "tomorrow" * fix: make an early return * fix: move callback outside of setState and make setState inline * fix: trigger onFilterSelected when operator changes * chore: make an early return * fix: use DateFormat * fix: replace variable with null * fix: return picked instead of _selectedDate * chore: make early return, rename variable and make statement inline * fix: remove setState and rename variable * fix: swap places of helper method with main method * fix: revert make method more readable * fix: move the setter's position * fix: remove setState * refactor: cleanup / make more readable --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Julian König <[email protected]> Co-authored-by: Timo Notheisen <[email protected]>
- Loading branch information
1 parent
ff91a68
commit d41de55
Showing
20 changed files
with
705 additions
and
19 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
* @tnotheis @jkoenig134 | ||
* @tnotheis @jkoenig134 |
96 changes: 96 additions & 0 deletions
96
AdminUi/apps/admin_ui/lib/core/widgets/filters/date_filter.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import 'package:admin_api_sdk/admin_api_sdk.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:intl/intl.dart'; | ||
|
||
import '/core/constants.dart'; | ||
import 'to_filter_operator_dropdown_menu_item.dart'; | ||
|
||
class DateFilter extends StatefulWidget { | ||
final void Function(FilterOperator operator, DateTime? selectedDate) onFilterSelected; | ||
final String label; | ||
|
||
const DateFilter({required this.onFilterSelected, required this.label, super.key}); | ||
|
||
@override | ||
State<DateFilter> createState() => _DateFilterState(); | ||
} | ||
|
||
class _DateFilterState extends State<DateFilter> { | ||
FilterOperator _operator = FilterOperator.equal; | ||
DateTime? _selectedDate; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Column( | ||
crossAxisAlignment: CrossAxisAlignment.start, | ||
children: [ | ||
Text( | ||
'${widget.label}:', | ||
style: const TextStyle(fontWeight: FontWeight.bold), | ||
), | ||
Gaps.h8, | ||
Row( | ||
children: [ | ||
DropdownButton<FilterOperator>( | ||
value: _operator, | ||
onChanged: (selectedOperator) { | ||
if (selectedOperator == null) return; | ||
setState(() => _operator = selectedOperator); | ||
widget.onFilterSelected(selectedOperator, _selectedDate); | ||
}, | ||
items: FilterOperator.values.toDropdownMenuItems(), | ||
), | ||
Gaps.w8, | ||
InkWell( | ||
onTap: _selectNewDate, | ||
child: Container( | ||
padding: const EdgeInsets.all(12), | ||
decoration: BoxDecoration( | ||
border: Border.all(color: Colors.grey), | ||
borderRadius: BorderRadius.circular(8), | ||
), | ||
child: Row( | ||
mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||
children: [ | ||
Text( | ||
_selectedDate != null ? DateFormat('yyyy-MM-dd').format(_selectedDate!) : 'Select date', | ||
style: const TextStyle(fontSize: 14), | ||
), | ||
Gaps.w8, | ||
const Icon(Icons.calendar_today), | ||
if (_selectedDate != null) ...[ | ||
Gaps.w8, | ||
GestureDetector( | ||
onTap: _clearDate, | ||
child: const Icon(Icons.clear, size: 20), | ||
), | ||
], | ||
], | ||
), | ||
), | ||
), | ||
], | ||
), | ||
], | ||
); | ||
} | ||
|
||
void _clearDate() { | ||
setState(() => _selectedDate = null); | ||
widget.onFilterSelected(_operator, null); | ||
} | ||
|
||
Future<void> _selectNewDate() async { | ||
final picked = await showDatePicker( | ||
context: context, | ||
initialDate: _selectedDate ?? DateTime.now(), | ||
firstDate: DateTime(2000), | ||
lastDate: DateTime.now().add(const Duration(days: 1)), | ||
); | ||
|
||
if (picked == null) return; | ||
|
||
setState(() => _selectedDate = picked); | ||
widget.onFilterSelected(_operator, picked); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export 'date_filter.dart'; | ||
export 'input_filter.dart'; | ||
export 'multi_select.dart'; | ||
export 'number_filter.dart'; |
31 changes: 31 additions & 0 deletions
31
AdminUi/apps/admin_ui/lib/core/widgets/filters/input_filter.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import 'package:flutter/material.dart'; | ||
|
||
import '/core/core.dart'; | ||
|
||
class InputField extends StatelessWidget { | ||
final void Function(String enteredText) onEnteredText; | ||
final String label; | ||
|
||
const InputField({required this.onEnteredText, required this.label, super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Column( | ||
crossAxisAlignment: CrossAxisAlignment.start, | ||
children: [ | ||
Text( | ||
'$label:', | ||
style: const TextStyle(fontWeight: FontWeight.bold), | ||
), | ||
Gaps.h8, | ||
SizedBox( | ||
width: 180, | ||
child: TextField( | ||
onChanged: onEnteredText, | ||
decoration: const InputDecoration(border: OutlineInputBorder()), | ||
), | ||
), | ||
], | ||
); | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
AdminUi/apps/admin_ui/lib/core/widgets/filters/multi_select.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:multi_dropdown/multiselect_dropdown.dart'; | ||
|
||
import '/core/core.dart'; | ||
|
||
class MultiSelectFilter extends StatelessWidget { | ||
final String label; | ||
final String searchLabel; | ||
final MultiSelectController<String> controller; | ||
final void Function(List<ValueItem<String>> selectedOptions) onOptionSelected; | ||
|
||
const MultiSelectFilter({ | ||
required this.label, | ||
required this.searchLabel, | ||
required this.controller, | ||
required this.onOptionSelected, | ||
super.key, | ||
}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Column( | ||
crossAxisAlignment: CrossAxisAlignment.start, | ||
children: [ | ||
Text( | ||
'$label:', | ||
style: const TextStyle(fontWeight: FontWeight.bold), | ||
), | ||
Gaps.h8, | ||
SizedBox( | ||
width: 250, | ||
child: MultiSelectDropDown( | ||
hint: '', | ||
searchLabel: searchLabel, | ||
searchEnabled: true, | ||
controller: controller, | ||
options: controller.options, | ||
fieldBackgroundColor: Theme.of(context).colorScheme.background, | ||
searchBackgroundColor: Theme.of(context).colorScheme.background, | ||
dropdownBackgroundColor: Theme.of(context).colorScheme.background, | ||
selectedOptionBackgroundColor: Theme.of(context).colorScheme.background, | ||
selectedOptionTextColor: Theme.of(context).colorScheme.onBackground, | ||
optionsBackgroundColor: Theme.of(context).colorScheme.background, | ||
optionTextStyle: TextStyle(color: Theme.of(context).colorScheme.onBackground), | ||
onOptionSelected: onOptionSelected, | ||
), | ||
), | ||
], | ||
); | ||
} | ||
} |
66 changes: 66 additions & 0 deletions
66
AdminUi/apps/admin_ui/lib/core/widgets/filters/number_filter.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import 'package:admin_api_sdk/admin_api_sdk.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter/services.dart'; | ||
|
||
import '/core/constants.dart'; | ||
import 'to_filter_operator_dropdown_menu_item.dart'; | ||
|
||
class NumberFilter extends StatefulWidget { | ||
final void Function(FilterOperator operator, String enteredValue) onNumberSelected; | ||
final String label; | ||
|
||
const NumberFilter({ | ||
required this.onNumberSelected, | ||
required this.label, | ||
super.key, | ||
}); | ||
|
||
@override | ||
State<NumberFilter> createState() => _NumberFilterState(); | ||
} | ||
|
||
class _NumberFilterState extends State<NumberFilter> { | ||
late FilterOperator _operator = FilterOperator.equal; | ||
late String _value = ''; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Column( | ||
crossAxisAlignment: CrossAxisAlignment.start, | ||
children: [ | ||
Text( | ||
'${widget.label}:', | ||
style: const TextStyle(fontWeight: FontWeight.bold), | ||
), | ||
Gaps.h8, | ||
Row( | ||
children: [ | ||
DropdownButton<FilterOperator>( | ||
value: _operator, | ||
onChanged: (selectedOperator) { | ||
if (selectedOperator == null) return; | ||
setState(() => _operator = selectedOperator); | ||
widget.onNumberSelected(selectedOperator, _value); | ||
}, | ||
items: FilterOperator.values.toDropdownMenuItems(), | ||
), | ||
Gaps.w16, | ||
SizedBox( | ||
width: 120, | ||
child: TextField( | ||
onChanged: (enteredValue) { | ||
_value = enteredValue; | ||
widget.onNumberSelected(_operator, enteredValue); | ||
}, | ||
decoration: const InputDecoration(border: OutlineInputBorder()), | ||
style: const TextStyle(fontSize: 12), | ||
inputFormatters: <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly], | ||
keyboardType: TextInputType.number, | ||
), | ||
), | ||
], | ||
), | ||
], | ||
); | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
AdminUi/apps/admin_ui/lib/core/widgets/filters/to_filter_operator_dropdown_menu_item.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import 'package:admin_api_sdk/admin_api_sdk.dart'; | ||
import 'package:flutter/material.dart'; | ||
|
||
extension ToFilterOperatorDropdownMenuItem on List<FilterOperator> { | ||
List<DropdownMenuItem<FilterOperator>> toDropdownMenuItems() => map( | ||
(operator) => DropdownMenuItem<FilterOperator>( | ||
value: operator, | ||
child: Text(operator.userFriendlyOperator), | ||
), | ||
).toList(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export 'app_title.dart'; | ||
export 'filters/filters.dart'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
91 changes: 91 additions & 0 deletions
91
AdminUi/apps/admin_ui/lib/home/identities_overview/identities_data_table_source.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import 'package:admin_api_sdk/admin_api_sdk.dart'; | ||
import 'package:data_table_2/data_table_2.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:get_it/get_it.dart'; | ||
import 'package:intl/intl.dart'; | ||
import 'package:logger/logger.dart'; | ||
|
||
class IdentityDataTableSource extends AsyncDataTableSource { | ||
Pagination? _pagination; | ||
var _sortingSettings = (sortColumnIndex: 0, sortAscending: true); | ||
|
||
IdentityOverviewFilter? _filter; | ||
|
||
set filter(IdentityOverviewFilter? newFilter) { | ||
if (_filter != newFilter) { | ||
_filter = newFilter; | ||
notifyListeners(); | ||
} | ||
} | ||
|
||
void sort({required int sortColumnIndex, required bool sortColumnAscending}) { | ||
_sortingSettings = (sortColumnIndex: sortColumnIndex, sortAscending: sortColumnAscending); | ||
notifyListeners(); | ||
} | ||
|
||
@override | ||
bool get isRowCountApproximate => false; | ||
|
||
@override | ||
int get rowCount => _pagination?.totalRecords ?? 0; | ||
|
||
@override | ||
int get selectedRowCount => 0; | ||
|
||
@override | ||
Future<AsyncRowsResponse> getRows(int startIndex, int count) async { | ||
final pageNumber = startIndex ~/ count; | ||
final orderBy = _getODataOrderBy(); | ||
|
||
try { | ||
final response = await GetIt.I.get<AdminApiClient>().identities.getIdentities( | ||
pageNumber: pageNumber, | ||
pageSize: count, | ||
filter: _filter, | ||
orderBy: orderBy, | ||
); | ||
_pagination = response.pagination; | ||
|
||
final rows = response.data.indexed | ||
.map( | ||
(identity) => DataRow.byIndex( | ||
index: pageNumber * count + identity.$1, | ||
cells: [ | ||
DataCell(Text(identity.$2.address)), | ||
DataCell(Text(identity.$2.tier.name)), | ||
DataCell(Text(identity.$2.createdWithClient)), | ||
DataCell(Text(identity.$2.numberOfDevices.toString())), | ||
DataCell(Text(DateFormat('yyyy-MM-dd').format(identity.$2.createdAt))), | ||
DataCell(Text(identity.$2.lastLoginAt != null ? DateFormat('yyyy-MM-dd').format(identity.$2.lastLoginAt!) : '')), | ||
DataCell(Text(identity.$2.datawalletVersion?.toString() ?? '')), | ||
DataCell(Text(identity.$2.identityVersion.toString())), | ||
], | ||
), | ||
) | ||
.toList(); | ||
|
||
return AsyncRowsResponse(response.pagination.totalRecords, rows); | ||
} catch (e) { | ||
GetIt.I.get<Logger>().e('Failed to load data: $e'); | ||
|
||
throw Exception('Failed to load data: $e'); | ||
} | ||
} | ||
|
||
String _getODataOrderBy() { | ||
final columnName = _getFieldNameByIndex(_sortingSettings.sortColumnIndex); | ||
final sortingDirection = _sortingSettings.sortAscending ? 'asc' : 'desc'; | ||
return '$columnName $sortingDirection'; | ||
} | ||
|
||
String _getFieldNameByIndex(int index) => switch (index) { | ||
0 => 'address', | ||
2 => 'createdWithClient', | ||
3 => 'numberOfDevices', | ||
4 => 'createdAt', | ||
5 => 'lastLoginAt', | ||
6 => 'datawalletVersion', | ||
7 => 'identityVersion', | ||
_ => throw Exception('Invalid column index') | ||
}; | ||
} |
Oops, something went wrong.