Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admin UI: Overview of all identities #593

Merged
merged 111 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
c7a88e7
feat: add data table 2
stamenione Apr 3, 2024
a78c2d4
Admin UI: Login page (#584)
stamenione Apr 3, 2024
231616b
Merge branch 'main' into NMSHDB-100-admin-ui-overview-of-all-identities
stamenione Apr 3, 2024
0c96b5e
feat: add multiselect
stamenione Apr 3, 2024
07e022c
feat: add data table 2 and multi dropdown
stamenione Apr 3, 2024
8be91b5
fix: remove dependencies from wrong pubspec
stamenione Apr 3, 2024
ea6126f
fix: remove filter when there are no filters applied
stamenione Apr 4, 2024
413e889
chore: add builders
stamenione Apr 5, 2024
e20f46f
chore: display the IdentityOverview
stamenione Apr 5, 2024
a649f3e
fix: change the widget
stamenione Apr 5, 2024
ce2d911
feat: add identity overview
stamenione Apr 5, 2024
b99519a
feat: add components
stamenione Apr 5, 2024
ab5f8d0
feat: add shared filter fields
stamenione Apr 5, 2024
6b652bc
chore: tier overview (wip)
stamenione Apr 8, 2024
aa8670e
chore: identity details (wip)
stamenione Apr 8, 2024
d901930
chore: pagination (wip)
stamenione Apr 8, 2024
12f4799
chore: update rowCount
jkoenig134 Apr 8, 2024
a80c188
Merge branch 'NMSHDB-100-admin-ui-overview-of-all-identities' of http…
stamenione Apr 8, 2024
75be191
fix: pagination
stamenione Apr 8, 2024
5482b02
fix: pagination
stamenione Apr 8, 2024
a08b85f
chore: remove comment and print
stamenione Apr 8, 2024
1564bbf
chore: remove comments
stamenione Apr 8, 2024
fef208f
Merge branch 'main' into NMSHDB-100-admin-ui-overview-of-all-identities
stamenione Apr 8, 2024
b71f118
fix: revert created files
stamenione Apr 8, 2024
dbf410c
Merge branch 'main' into NMSHDB-100-admin-ui-overview-of-all-identities
mergify[bot] Apr 9, 2024
2a6df4a
Merge branch 'main' into NMSHDB-100-admin-ui-overview-of-all-identities
mergify[bot] Apr 10, 2024
eed4069
Merge branch 'main' into NMSHDB-100-admin-ui-overview-of-all-identities
mergify[bot] Apr 10, 2024
b284015
Merge branch 'main' into NMSHDB-100-admin-ui-overview-of-all-identities
mergify[bot] Apr 12, 2024
43bab0b
Merge branch 'main' into NMSHDB-100-admin-ui-overview-of-all-identities
stamenione Apr 16, 2024
3106965
Merge branch 'NMSHDB-100-admin-ui-overview-of-all-identities' of http…
stamenione Apr 16, 2024
2ee8a71
Merge branch 'main' into NMSHDB-100-admin-ui-overview-of-all-identities
mergify[bot] Apr 17, 2024
fd645d8
Merge branch 'main' into NMSHDB-100-admin-ui-overview-of-all-identities
mergify[bot] Apr 18, 2024
be3de6d
Merge branch 'main' into NMSHDB-100-admin-ui-overview-of-all-identities
mergify[bot] Apr 19, 2024
cbcd3de
Merge branch 'main' into NMSHDB-100-admin-ui-overview-of-all-identities
mergify[bot] Apr 22, 2024
b0e234c
Merge branch 'main' into NMSHDB-100-admin-ui-overview-of-all-identities
mergify[bot] Apr 22, 2024
c7e8e91
Merge branch 'main' into NMSHDB-100-admin-ui-overview-of-all-identities
mergify[bot] Apr 23, 2024
12343f8
Merge branch 'main' into NMSHDB-100-admin-ui-overview-of-all-identities
mergify[bot] Apr 23, 2024
7140885
chore: use switch expression
stamenione Apr 24, 2024
690d20c
chore: use gaps 16 and enlarge the textfield to 120
stamenione Apr 24, 2024
a6a86ad
chore: remove unused controller
stamenione Apr 24, 2024
36fe7f1
chore: use async paginated data table 2
stamenione Apr 24, 2024
9d00d4a
chore: rename variables and add this.*
stamenione Apr 24, 2024
141310f
Merge branch 'main' into NMSHDB-100-admin-ui-overview-of-all-identities
mergify[bot] Apr 24, 2024
5d287d6
Merge branch 'NMSHDB-100-admin-ui-overview-of-all-identities' of http…
stamenione Apr 24, 2024
bcfb93c
chore: change the named parameter
stamenione Apr 24, 2024
2c83048
chore: change the color depending of a theme mode
stamenione Apr 24, 2024
f389fe5
chore: rename a label
stamenione Apr 24, 2024
6787752
chore: remove comment
stamenione Apr 24, 2024
4a3d9fe
chore: move files into a new folder
stamenione Apr 24, 2024
03195d8
chore: remove components.dart and change the import
stamenione Apr 24, 2024
29d4b51
chore: move the file in the new folder and implement async data table…
stamenione Apr 24, 2024
f721b42
chore: add trailing commas
stamenione Apr 24, 2024
bdc49a7
refactor: change code structure / remove empty widget
jkoenig134 Apr 24, 2024
68803c6
chore: add logger
jkoenig134 Apr 24, 2024
9ce0706
fix: data loading
jkoenig134 Apr 24, 2024
e75f275
chore: make selected date nullable and remove isDateSelected
stamenione Apr 24, 2024
0753ee8
chore: make variables private and remove late
stamenione Apr 24, 2024
17aa9ee
fix: overhaul sorting and filtering
jkoenig134 Apr 24, 2024
e19c2bb
Merge branch 'NMSHDB-100-admin-ui-overview-of-all-identities' of gith…
jkoenig134 Apr 24, 2024
aa1e52e
fix: add empty hint
jkoenig134 Apr 24, 2024
06c8df0
fix: remove dynamic
jkoenig134 Apr 24, 2024
7347329
chore: add set state
stamenione Apr 24, 2024
4b4319c
chore: remove onDateSelected from setState
stamenione Apr 24, 2024
6a75363
chore: pageNumber should start from 0
stamenione Apr 24, 2024
a03a8a9
chore: trigger order by from odata request
stamenione Apr 24, 2024
b48e915
chore: remove required
stamenione Apr 24, 2024
3f21e81
chore: make constructor inline
stamenione Apr 24, 2024
4c19a85
chore: remove unnecessary setState
stamenione Apr 24, 2024
d76c1e8
chore: rename the method
stamenione Apr 24, 2024
0b155c3
chore: add onOptionRemoved and make the dropdown wider
stamenione Apr 24, 2024
48ee371
fix: revert remove the onOptionRemoved
stamenione Apr 24, 2024
a30f189
fix: remove comments
stamenione Apr 24, 2024
7ffc03f
fix: use map instead of List<_Operator>
stamenione Apr 24, 2024
e1a9b08
fix: remove unused scrollController
stamenione Apr 24, 2024
4af26d0
Merge branch 'main' into NMSHDB-100-admin-ui-overview-of-all-identities
mergify[bot] Apr 25, 2024
306ac68
feat: add intl dependency
stamenione Apr 25, 2024
a283b23
chore: add optional and remove unnecessary variables and method
stamenione Apr 25, 2024
0ae4b16
chore: use absent method
stamenione Apr 25, 2024
179dc73
fix: change substring to date format
stamenione Apr 25, 2024
ba708b7
fix: use lastLoginAt
stamenione Apr 25, 2024
08cda7c
fix: add a statement to check whether the lastLoginAt is null or not
stamenione Apr 25, 2024
b700108
refactor: simplify
jkoenig134 Apr 25, 2024
b3ca73b
chore: remove unused field
jkoenig134 Apr 25, 2024
ab37ce9
chore: remove the unused from method
jkoenig134 Apr 25, 2024
cc42086
fix: make method inline
stamenione Apr 25, 2024
06e882c
fix: change to named parameter
stamenione Apr 25, 2024
5c73a9d
fix: make field private
stamenione Apr 25, 2024
145aad5
fix: undo the change
stamenione Apr 25, 2024
aa926b4
fix: make method inline
stamenione Apr 25, 2024
78b0a7a
fix: use record
stamenione Apr 26, 2024
0e10329
fix: rename variables
stamenione Apr 26, 2024
478fcc0
fix: use setter
stamenione Apr 26, 2024
4b3e9ad
fix: resolve ! for newValue
stamenione Apr 26, 2024
5ef55c4
fix: move callback outside of setState
stamenione Apr 26, 2024
2349eac
fix: rename a method
stamenione Apr 26, 2024
b7ae3e7
fix: maximum date that can be picked is "tomorrow"
stamenione Apr 26, 2024
213ce73
fix: make an early return
stamenione Apr 26, 2024
256cdfb
fix: move callback outside of setState and make setState inline
stamenione Apr 26, 2024
57a0c13
fix: trigger onFilterSelected when operator changes
stamenione Apr 26, 2024
7d605f6
Merge branch 'main' into NMSHDB-100-admin-ui-overview-of-all-identities
mergify[bot] Apr 26, 2024
e218854
chore: make an early return
stamenione Apr 26, 2024
ca84f48
fix: use DateFormat
stamenione Apr 26, 2024
b86be89
fix: replace variable with null
stamenione Apr 26, 2024
39c6a12
fix: return picked instead of _selectedDate
stamenione Apr 26, 2024
fd120b2
chore: make early return, rename variable and make statement inline
stamenione Apr 26, 2024
6179b90
fix: remove setState and rename variable
stamenione Apr 26, 2024
35a15c7
fix: swap places of helper method with main method
stamenione Apr 26, 2024
3e6c26c
fix: revert make method more readable
stamenione Apr 26, 2024
842f952
fix: move the setter's position
stamenione Apr 26, 2024
e1d6741
fix: remove setState
stamenione Apr 26, 2024
6801773
refactor: cleanup / make more readable
jkoenig134 Apr 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @tnotheis @jkoenig134
* @tnotheis @jkoenig134
96 changes: 96 additions & 0 deletions AdminUi/apps/admin_ui/lib/core/widgets/filters/date_filter.dart
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);
}
}
4 changes: 4 additions & 0 deletions AdminUi/apps/admin_ui/lib/core/widgets/filters/filters.dart
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';
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()),
),
),
],
);
}
}
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,
),
),
],
);
}
}
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,
),
),
],
),
],
);
}
}
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();
}
1 change: 1 addition & 0 deletions AdminUi/apps/admin_ui/lib/core/widgets/widgets.dart
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export 'app_title.dart';
export 'filters/filters.dart';
9 changes: 1 addition & 8 deletions AdminUi/apps/admin_ui/lib/home/home.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import 'package:flutter/material.dart';

class Identities extends StatelessWidget {
const Identities({super.key});

@override
Widget build(BuildContext context) {
return const Placeholder();
}
}
export 'identities_overview/identities_overview.dart';

class Tiers extends StatelessWidget {
const Tiers({super.key});
Expand Down
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();
}
}
stamenione marked this conversation as resolved.
Show resolved Hide resolved

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')
};
}
Loading