2
0
mirror of https://github.com/inventree/inventree-app.git synced 2026-03-11 04:34:29 +00:00

Fix bool fields (#778)

* Improved UX for boolean fields

- Use segmented button
- Allow tristate
- Improved filtering options

* Bug fix for null filter values

* Prevent null filters from being sent to the server

* Update release notes
This commit is contained in:
Oliver
2026-02-27 13:55:08 +11:00
committed by GitHub
parent 286daf2567
commit 04f98559fc
4 changed files with 97 additions and 19 deletions

View File

@@ -2,6 +2,9 @@
--- ---
- Auto-fill location data when receiving item via barcode scan - Auto-fill location data when receiving item via barcode scan
- Visual improvements for boolean form fields
- Add support for tri-state boolean form fields
- Bug fixes for refreshing list view data
## 0.22.2 - February 2026 ## 0.22.2 - February 2026
--- ---

View File

@@ -874,25 +874,86 @@ class APIFormField {
// Construct a boolean input element // Construct a boolean input element
Widget _constructBoolean() { Widget _constructBoolean() {
bool? initial_value; String initial_value = "null";
if (value is bool || value == null) { bool allow_null = (getParameter("tristate") ?? false) as bool;
initial_value = value as bool?;
if (value is bool) {
initial_value = value.toString().toLowerCase();
} else if (value == null) {
if (allow_null) {
initial_value = "null";
} else { } else {
String vs = value.toString().toLowerCase(); initial_value = "false";
initial_value = ["1", "true", "yes"].contains(vs); }
} else {
// Not a boolean value - may be a string
if (["1", "true", "yes"].contains(value.toString().toLowerCase())) {
initial_value = "true";
} else if ([
"0",
"false",
"no",
].contains(value.toString().toLowerCase())) {
initial_value = "false";
} else if (allow_null) {
initial_value = "null";
} else {
initial_value = "false";
}
} }
return CheckBoxField( List<ButtonSegment<String>> buttons = [];
label: label,
labelStyle: _labelStyle(), if ((getParameter("tristate") ?? false) as bool) {
helperText: helpText, buttons.add(
helperStyle: _helperStyle(), ButtonSegment<String>(
initial: initial_value, value: "null",
tristate: (getParameter("tristate") ?? false) as bool, icon: Icon(TablerIcons.minus, color: COLOR_GRAY_LIGHT),
onSaved: (val) { ),
setFieldValue(val); );
}
buttons.add(
ButtonSegment<String>(
value: "false",
icon: Icon(TablerIcons.x, color: COLOR_DANGER),
),
);
buttons.add(
ButtonSegment<String>(
value: "true",
icon: Icon(TablerIcons.check, color: COLOR_SUCCESS),
),
);
return ListTile(
title: Text(label),
contentPadding: EdgeInsets.zero,
subtitle: Text(helpText),
trailing: SegmentedButton<String>(
segments: buttons,
selected: {initial_value},
showSelectedIcon: false,
multiSelectionEnabled: false,
style: SegmentedButton.styleFrom(
padding: EdgeInsets.all(0),
// minimumSize: MaterialStateProperty.all(Size(0, 0)),
// tapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: VisualDensity.compact,
),
onSelectionChanged: (Set<String> selection) {
String element = selection.first;
if (element == "null" && allow_null) {
setFieldValue(null);
} else if (element == "true") {
setFieldValue(true);
} else {
setFieldValue(false);
}
}, },
),
); );
} }
@@ -1168,7 +1229,9 @@ class APIFormWidgetState extends State<APIFormWidget> {
// Callback for when a field value is changed // Callback for when a field value is changed
// Default implementation does nothing, // Default implementation does nothing,
// but custom form implementations may override this function // but custom form implementations may override this function
void onValueChanged(String field, dynamic value) {} void onValueChanged(String field, dynamic value) {
setState(() {});
}
Future<void> handleSuccess( Future<void> handleSuccess(
Map<String, dynamic> submittedData, Map<String, dynamic> submittedData,

View File

@@ -45,6 +45,8 @@ class LabelFormWidgetState extends APIFormWidgetState {
if (field == "plugin") { if (field == "plugin") {
onPluginChanged(value.toString()); onPluginChanged(value.toString());
} }
super.onValueChanged(field, value);
} }
@override @override

View File

@@ -92,10 +92,11 @@ abstract class PaginatedSearchState<T extends PaginatedSearchWidget>
// Skip null values // Skip null values
if (value == null) { if (value == null) {
continue; f[k] = "null";
} } else {
f[k] = value.toString(); f[k] = value.toString();
} }
}
return f; return f;
} }
@@ -341,7 +342,16 @@ abstract class PaginatedSearchState<T extends PaginatedSearchWidget>
Map<String, String> f = await constructFilters(); Map<String, String> f = await constructFilters();
if (f.isNotEmpty) { if (f.isNotEmpty) {
params.addAll(f); for (String k in f.keys) {
// Remove any existing filter keys
dynamic value = f[k];
if (value == null || value == "null") {
params.remove(k);
} else {
params[k] = value.toString();
}
}
} }
final page = await requestPage(_pageSize, pageKey, params); final page = await requestPage(_pageSize, pageKey, params);