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:
@@ -2,6 +2,9 @@
|
||||
---
|
||||
|
||||
- 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
|
||||
---
|
||||
|
||||
@@ -874,25 +874,86 @@ class APIFormField {
|
||||
|
||||
// Construct a boolean input element
|
||||
Widget _constructBoolean() {
|
||||
bool? initial_value;
|
||||
String initial_value = "null";
|
||||
|
||||
if (value is bool || value == null) {
|
||||
initial_value = value as bool?;
|
||||
bool allow_null = (getParameter("tristate") ?? false) as bool;
|
||||
|
||||
if (value is bool) {
|
||||
initial_value = value.toString().toLowerCase();
|
||||
} else if (value == null) {
|
||||
if (allow_null) {
|
||||
initial_value = "null";
|
||||
} else {
|
||||
String vs = value.toString().toLowerCase();
|
||||
initial_value = ["1", "true", "yes"].contains(vs);
|
||||
initial_value = "false";
|
||||
}
|
||||
} 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(
|
||||
label: label,
|
||||
labelStyle: _labelStyle(),
|
||||
helperText: helpText,
|
||||
helperStyle: _helperStyle(),
|
||||
initial: initial_value,
|
||||
tristate: (getParameter("tristate") ?? false) as bool,
|
||||
onSaved: (val) {
|
||||
setFieldValue(val);
|
||||
List<ButtonSegment<String>> buttons = [];
|
||||
|
||||
if ((getParameter("tristate") ?? false) as bool) {
|
||||
buttons.add(
|
||||
ButtonSegment<String>(
|
||||
value: "null",
|
||||
icon: Icon(TablerIcons.minus, color: COLOR_GRAY_LIGHT),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
// Default implementation does nothing,
|
||||
// but custom form implementations may override this function
|
||||
void onValueChanged(String field, dynamic value) {}
|
||||
void onValueChanged(String field, dynamic value) {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
Future<void> handleSuccess(
|
||||
Map<String, dynamic> submittedData,
|
||||
|
||||
@@ -45,6 +45,8 @@ class LabelFormWidgetState extends APIFormWidgetState {
|
||||
if (field == "plugin") {
|
||||
onPluginChanged(value.toString());
|
||||
}
|
||||
|
||||
super.onValueChanged(field, value);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -92,10 +92,11 @@ abstract class PaginatedSearchState<T extends PaginatedSearchWidget>
|
||||
|
||||
// Skip null values
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
f[k] = "null";
|
||||
} else {
|
||||
f[k] = value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
@@ -341,7 +342,16 @@ abstract class PaginatedSearchState<T extends PaginatedSearchWidget>
|
||||
Map<String, String> f = await constructFilters();
|
||||
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user