feat: add initial due date support in task form screen fix: ensure completed tasks are displayed correctly in tasks list screen fix: resolve layout issues in ticket detail screen buttons fix: improve error messaging for network failures in API service fix: handle organization data correctly in auth service fix: update task row to prevent layout issues with dismissible actions fix: ensure multi-select sheet buttons layout correctly test: add tests for KPI cards in dashboard screen test: add tests for deals filter sheet layout test: add tests for solution detail screen access control test: add tests for task create date functionality test: add tests for task row dismissible behavior test: add tests for tasks list screen behavior with completed tasks test: add tests for API envelope handling test: add tests for API service network failure handling test: add tests for permissions logic
39 lines
1.4 KiB
Dart
39 lines
1.4 KiB
Dart
/// Pulling a list out of a response body, whatever the endpoint calls it.
|
|
///
|
|
/// The API has no single list envelope. `/api/contacts/` publishes `results`,
|
|
/// `/api/users/get-teams-and-users/` publishes `profiles` and `teams`, and
|
|
/// `/api/accounts/` nests its rows two deep under
|
|
/// `active_accounts.open_accounts`. Each caller used to guess with a chain of
|
|
/// `if (data['x'] != null)`, which fails silently: a wrong guess yields an
|
|
/// empty list and a picker with nothing in it, not an error anyone sees.
|
|
///
|
|
/// Passing the paths explicitly keeps the guess in one place and makes it
|
|
/// testable against the real shape.
|
|
library;
|
|
|
|
/// The first list found at any of [paths], or `[]` if none matched.
|
|
///
|
|
/// A path is dot-separated and may descend through nested maps, so
|
|
/// `'active_accounts.open_accounts'` is valid. Order matters: the first path
|
|
/// that resolves to a list wins, so put the endpoint's real key first and any
|
|
/// tolerated alternatives after it.
|
|
List<Map<String, dynamic>> listFromEnvelope(
|
|
Map<String, dynamic> data,
|
|
List<String> paths,
|
|
) {
|
|
for (final path in paths) {
|
|
dynamic node = data;
|
|
for (final segment in path.split('.')) {
|
|
if (node is Map<String, dynamic> && node.containsKey(segment)) {
|
|
node = node[segment];
|
|
} else {
|
|
node = null;
|
|
break;
|
|
}
|
|
}
|
|
if (node is List) {
|
|
return node.whereType<Map<String, dynamic>>().toList();
|
|
}
|
|
}
|
|
return const [];
|
|
}
|