2
0
mirror of https://github.com/inventree/inventree-app.git synced 2025-04-28 05:26:47 +00:00

Search improvements (#388)

* Refactor search widget

- visual improvements
- Simplifications
- Add refresh button
- Improve search button

* Track original search

* fix BOM widget

* Update release notes
This commit is contained in:
Oliver 2023-06-30 22:42:59 +10:00 committed by GitHub
parent 23abcb48f2
commit 138cae2da0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 158 additions and 211 deletions

View File

@ -2,7 +2,7 @@
---
- Pre-fill stock location when transferring stock amount
- UX improvements for searching data
### - 0.12.3 - June 2023
---

View File

@ -117,7 +117,16 @@ class InvenTreeStockItemHistory extends InvenTreeModel {
}
}
String get userString => getString("username", subKey: "user_detail");
int? get user => getValue("user") as int?;
String get userString {
if (user != null) {
return getString("username", subKey: "user_detail");
} else {
return "";
}
}
}

View File

@ -80,7 +80,6 @@ class _BillOfMaterialsState extends RefreshableState<BillOfMaterialsWidget> {
Expanded(
child: PaginatedBomList(
filters,
showSearch: showFilterOptions,
isParentPart: widget.isParentComponent,
),
),
@ -95,10 +94,13 @@ class _BillOfMaterialsState extends RefreshableState<BillOfMaterialsWidget> {
*/
class PaginatedBomList extends PaginatedSearchWidget {
const PaginatedBomList(Map<String, String> filters, {bool showSearch = false, this.isParentPart = true}) : super(filters: filters, showSearch: showSearch);
const PaginatedBomList(Map<String, String> filters, {this.isParentPart = true}) : super(filters: filters);
final bool isParentPart;
@override
String get searchTitle => L10().parts;
@override
_PaginatedBomListState createState() => _PaginatedBomListState();
}

View File

@ -30,8 +30,6 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
_CategoryDisplayState();
bool showFilterOptions = false;
@override
String getAppBarTitle() => L10().partCategory;
@ -204,26 +202,12 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
List<Widget> tiles = <Widget>[
getCategoryDescriptionCard(),
ListTile(
title: Text(
L10().subcategories,
style: TextStyle(fontWeight: FontWeight.bold)
),
trailing: GestureDetector(
child: FaIcon(FontAwesomeIcons.filter),
onTap: () async {
setState(() {
showFilterOptions = !showFilterOptions;
});
},
)
),
Expanded(
child: PaginatedPartCategoryList(
{
"parent": widget.category?.pk.toString() ?? "null"
},
showFilterOptions,
{
"parent": widget.category?.pk.toString() ?? "null"
},
title: L10().subcategories,
),
flex: 10,
)
@ -240,25 +224,8 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
};
return [
ListTile(
title: Text(
L10().parts,
style: TextStyle(fontWeight: FontWeight.bold),
),
trailing: GestureDetector(
child: FaIcon(FontAwesomeIcons.filter),
onTap: () async {
setState(() {
showFilterOptions = !showFilterOptions;
});
},
),
),
Expanded(
child: PaginatedPartList(
filters,
showFilterOptions,
),
child: PaginatedPartList(filters),
flex: 10,
)
];

View File

@ -1,5 +1,4 @@
import "package:flutter/material.dart";
import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:inventree/inventree/model.dart";
import "package:inventree/inventree/part.dart";
@ -26,32 +25,21 @@ class _PartCategoryListState extends RefreshableState<PartCategoryList> {
_PartCategoryListState();
bool showFilterOptions = false;
@override
List<Widget> appBarActions(BuildContext context) => [
IconButton(
icon: FaIcon(FontAwesomeIcons.filter),
onPressed: () async {
setState(() {
showFilterOptions = !showFilterOptions;
});
},
)
];
@override
String getAppBarTitle() => L10().partCategories;
@override
Widget getBody(BuildContext context) {
return PaginatedPartCategoryList(widget.filters, showFilterOptions);
return PaginatedPartCategoryList(widget.filters);
}
}
class PaginatedPartCategoryList extends PaginatedSearchWidget {
const PaginatedPartCategoryList(Map<String, String> filters, bool showSearch) : super(filters: filters, showSearch: showSearch);
const PaginatedPartCategoryList(Map<String, String> filters, {String title = ""}) : super(filters: filters, title: title);
@override
String get searchTitle => title.isNotEmpty ? title : L10().partCategories;
@override
_PaginatedPartCategoryListState createState() => _PaginatedPartCategoryListState();

View File

@ -36,14 +36,19 @@ class _CompanyListWidgetState extends RefreshableState<CompanyListWidget> {
@override
Widget getBody(BuildContext context) {
return PaginatedCompanyList(widget.filters, true);
return PaginatedCompanyList(widget.title, widget.filters);
}
}
class PaginatedCompanyList extends PaginatedSearchWidget {
const PaginatedCompanyList(Map<String, String> filters, bool showSearch) : super(filters: filters, showSearch: showSearch);
const PaginatedCompanyList(this.companyTitle, Map<String, String> filters) : super(filters: filters);
final String companyTitle;
@override
String get searchTitle => companyTitle;
@override
_CompanyListState createState() => _CompanyListState();

View File

@ -38,8 +38,6 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
final InvenTreeStockLocation? location;
bool showFilterOptions = false;
@override
String getAppBarTitle() {
return L10().stockLocation;
@ -345,26 +343,12 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
List<Widget> detailTiles() {
List<Widget> tiles = [
locationDescriptionCard(),
ListTile(
title: Text(
L10().sublocations,
style: TextStyle(fontWeight: FontWeight.bold),
),
trailing: GestureDetector(
child: FaIcon(FontAwesomeIcons.filter),
onTap: () async {
setState(() {
showFilterOptions = !showFilterOptions;
});
},
)
),
Expanded(
child: PaginatedStockLocationList(
{
"parent": location?.pk.toString() ?? "null",
},
showFilterOptions,
title: L10().sublocations,
),
flex: 10,
)
@ -380,25 +364,8 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
};
return [
ListTile(
title: Text(
L10().stock,
style: TextStyle(fontWeight: FontWeight.bold),
),
trailing: GestureDetector(
child: FaIcon(FontAwesomeIcons.filter),
onTap: () async {
setState(() {
showFilterOptions = !showFilterOptions;
});
},
),
),
Expanded(
child: PaginatedStockItemList(
filters,
showFilterOptions,
),
child: PaginatedStockItemList(filters),
flex: 10,
)
];

View File

@ -1,5 +1,4 @@
import "package:flutter/material.dart";
import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:inventree/inventree/model.dart";
import "package:inventree/inventree/stock.dart";
@ -27,33 +26,22 @@ class _StockLocationListState extends RefreshableState<StockLocationList> {
final Map<String, String> filters;
bool showFilterOptions = false;
@override
List<Widget> appBarActions(BuildContext context) => [
IconButton(
icon: FaIcon(FontAwesomeIcons.filter),
onPressed: () async {
setState(() {
showFilterOptions = !showFilterOptions;
});
},
)
];
@override
String getAppBarTitle() => L10().stockLocations;
@override
Widget getBody(BuildContext context) {
return PaginatedStockLocationList(filters, showFilterOptions);
return PaginatedStockLocationList(filters);
}
}
class PaginatedStockLocationList extends PaginatedSearchWidget {
const PaginatedStockLocationList(Map<String, String> filters, bool showSearch) : super(filters: filters, showSearch: showSearch);
const PaginatedStockLocationList(Map<String, String> filters, {String title = ""}) : super(filters: filters, title: title);
@override
String get searchTitle => title.isNotEmpty ? title : L10().stockLocations;
@override
_PaginatedStockLocationListState createState() => _PaginatedStockLocationListState();

View File

@ -19,11 +19,13 @@ import "package:inventree/widget/refreshable_state.dart";
*/
abstract class PaginatedSearchWidget extends StatefulWidget {
const PaginatedSearchWidget({this.filters = const {}, this.showSearch = false});
const PaginatedSearchWidget({this.filters = const {}, this.title = ""});
final String title;
String get searchTitle => title;
final Map<String, String> filters;
final bool showSearch;
}
@ -34,6 +36,8 @@ abstract class PaginatedSearchState<T extends PaginatedSearchWidget> extends Sta
static const _pageSize = 25;
bool showSearchWidget = false;
// Prefix for storing and loading pagination options
// Override in implementing class
String get prefix => "prefix_";
@ -116,7 +120,7 @@ abstract class PaginatedSearchState<T extends PaginatedSearchWidget> extends Sta
}
// Update the (configurable) filters for this paginated list
Future<void> _saveOrderingOptions(BuildContext context) async {
Future<void> _setOrderingOptions(BuildContext context) async {
// Retrieve stored setting
dynamic _field = await orderingField();
dynamic _order = await orderingOrder();
@ -281,7 +285,17 @@ abstract class PaginatedSearchState<T extends PaginatedSearchWidget> extends Sta
// Include user search term
if (searchTerm.isNotEmpty) {
params["search"] = "${searchTerm}";
String _search = searchTerm;
// Include original search in search test
String original = params["original_search"] ?? "";
if (original.isNotEmpty) {
_search = "${original} ${_search}";
}
params["search"] = "${_search}";
}
// Use custom query ordering if available
@ -369,9 +383,12 @@ abstract class PaginatedSearchState<T extends PaginatedSearchWidget> extends Sta
@override
Widget build (BuildContext context) {
List<Widget> children = [];
List<Widget> children = [
buildTitleWidget(context),
Divider(),
];
if (widget.showSearch) {
if (showSearchWidget) {
children.add(buildSearchInput(context));
}
@ -392,7 +409,7 @@ abstract class PaginatedSearchState<T extends PaginatedSearchWidget> extends Sta
return NoResultsWidget(noResultsText);
}
),
separatorBuilder: (context, item) => const Divider(height: 1),
separatorBuilder: (context, item) => const Divider(height: .1),
)
]
)
@ -405,17 +422,65 @@ abstract class PaginatedSearchState<T extends PaginatedSearchWidget> extends Sta
);
}
/*
* Build the title widget for this list
*/
Widget buildTitleWidget(BuildContext context) {
const double icon_size = 32;
List<Widget> _icons = [];
if (filterOptions.isNotEmpty || orderingOptions.isNotEmpty) {
_icons.add(IconButton(
onPressed: () async {
_setOrderingOptions(context);
},
icon: Icon(Icons.filter_alt, size: icon_size)
));
}
_icons.add(IconButton(
onPressed: () {
setState(() {
showSearchWidget = !showSearchWidget;
});
},
icon: Icon(showSearchWidget ? Icons.zoom_out : Icons.search, size: icon_size)
));
_icons.add(IconButton(
onPressed: () async {
updateSearchTerm();
},
icon: Icon(Icons.refresh, size: icon_size),
));
return ListTile(
title: Text(
widget.searchTitle,
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
subtitle: Text(
"${L10().results}: ${resultCount}",
style: TextStyle(
fontStyle: FontStyle.italic
),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: _icons,
),
);
}
/*
* Construct a search input text field for the user to enter a search term
*/
Widget buildSearchInput(BuildContext context) {
return ListTile(
leading: orderingOptions.isEmpty ? null : GestureDetector(
child: Icon(Icons.filter_list, color: COLOR_ACTION, size: 32),
onTap: () async {
_saveOrderingOptions(context);
},
),
trailing: GestureDetector(
child: FaIcon(
searchController.text.isEmpty ? FontAwesomeIcons.magnifyingGlass : FontAwesomeIcons.deleteLeft,
@ -435,7 +500,6 @@ abstract class PaginatedSearchState<T extends PaginatedSearchWidget> extends Sta
},
decoration: InputDecoration(
hintText: L10().search,
helperText: resultsString(),
),
)
);

View File

@ -681,11 +681,11 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
).toList()
)
),
PaginatedStockItemList({"part": part.pk.toString()}, true)
PaginatedStockItemList({"part": part.pk.toString()})
];
if (showParameters) {
tabs.add(PaginatedParameterList({"part": part.pk.toString()}, true));
tabs.add(PaginatedParameterList({"part": part.pk.toString()}));
}
return tabs;

View File

@ -1,5 +1,4 @@
import "package:flutter/material.dart";
import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:inventree/api.dart";
import "package:inventree/l10.dart";
@ -38,21 +37,9 @@ class _PartListState extends RefreshableState<PartList> {
@override
String getAppBarTitle() => title.isNotEmpty ? title : L10().parts;
@override
List<Widget> appBarActions(BuildContext context) => [
IconButton(
icon: FaIcon(FontAwesomeIcons.filter),
onPressed: () async {
setState(() {
showFilterOptions = !showFilterOptions;
});
},
)
];
@override
Widget getBody(BuildContext context) {
return PaginatedPartList(filters, showFilterOptions);
return PaginatedPartList(filters);
}
}
@ -60,7 +47,10 @@ class _PartListState extends RefreshableState<PartList> {
class PaginatedPartList extends PaginatedSearchWidget {
const PaginatedPartList(Map<String, String> filters, bool showSearch) : super(filters: filters, showSearch: showSearch);
const PaginatedPartList(Map<String, String> filters) : super(filters: filters);
@override
String get searchTitle => L10().parts;
@override
_PaginatedPartListState createState() => _PaginatedPartListState();

View File

@ -44,10 +44,7 @@ class _ParameterWidgetState extends RefreshableState<PartParameterWidget> {
return Column(
children: [
Expanded(
child: PaginatedParameterList(
filters,
false,
)
child: PaginatedParameterList(filters)
)
],
);
@ -60,7 +57,10 @@ class _ParameterWidgetState extends RefreshableState<PartParameterWidget> {
*/
class PaginatedParameterList extends PaginatedSearchWidget {
const PaginatedParameterList(Map<String, String> filters, bool showSearch) : super(filters: filters, showSearch: showSearch);
const PaginatedParameterList(Map<String, String> filters) : super(filters: filters);
@override
String get searchTitle => L10().parts;
@override
_PaginatedParameterState createState() => _PaginatedParameterState();

View File

@ -17,7 +17,10 @@ import "package:inventree/widget/progress.dart";
*/
class PaginatedPOLineList extends PaginatedSearchWidget {
const PaginatedPOLineList(Map<String, String> filters, bool showSearch) : super(filters: filters, showSearch: showSearch);
const PaginatedPOLineList(Map<String, String> filters) : super(filters: filters);
@override
String get searchTitle => L10().lineItems;
@override
_PaginatedPOLineListState createState() => _PaginatedPOLineListState();

View File

@ -331,9 +331,9 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
List<Widget> getTabs(BuildContext context) {
return [
ListView(children: orderTiles(context)),
PaginatedPOLineList({"order": order.pk.toString()}, true),
PaginatedPOLineList({"order": order.pk.toString()}),
// ListView(children: lineTiles(context)),
PaginatedStockItemList({"purchase_order": order.pk.toString()}, true),
PaginatedStockItemList({"purchase_order": order.pk.toString()}),
];
}

View File

@ -31,23 +31,9 @@ class _PurchaseOrderListWidgetState extends RefreshableState<PurchaseOrderListWi
final Map<String, String> filters;
bool showFilterOptions = false;
@override
String getAppBarTitle() => L10().purchaseOrders;
@override
List<Widget> appBarActions(BuildContext context) => [
IconButton(
icon: FaIcon(FontAwesomeIcons.filter),
onPressed: () async {
setState(() {
showFilterOptions = !showFilterOptions;
});
},
)
];
@override
List<SpeedDialChild> actionButtons(BuildContext context) {
List<SpeedDialChild> actions = [];
@ -95,14 +81,17 @@ class _PurchaseOrderListWidgetState extends RefreshableState<PurchaseOrderListWi
@override
Widget getBody(BuildContext context) {
return PaginatedPurchaseOrderList(filters, showFilterOptions);
return PaginatedPurchaseOrderList(filters);
}
}
class PaginatedPurchaseOrderList extends PaginatedSearchWidget {
const PaginatedPurchaseOrderList(Map<String, String> filters, bool showSearch) : super(filters: filters, showSearch: showSearch);
const PaginatedPurchaseOrderList(Map<String, String> filters) : super(filters: filters);
@override
String get searchTitle => L10().purchaseOrders;
@override
_PaginatedPurchaseOrderListState createState() => _PaginatedPurchaseOrderListState();

View File

@ -270,8 +270,7 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> with
appBar: buildAppBar(context, refreshableKey),
drawer: getDrawer(context),
floatingActionButton: buildSpeedDial(context),
floatingActionButtonLocation: FloatingActionButtonLocation
.miniEndDocked,
floatingActionButtonLocation: FloatingActionButtonLocation.miniEndDocked,
body: RefreshIndicator(
onRefresh: () async {
refresh(context);

View File

@ -22,8 +22,6 @@ class _StockItemHistoryDisplayState extends RefreshableState<StockItemHistoryWid
final InvenTreeStockItem item;
bool showFilterOptions = false;
@override
String getAppBarTitle() => L10().stockItemHistory;
@ -36,7 +34,7 @@ class _StockItemHistoryDisplayState extends RefreshableState<StockItemHistoryWid
"item": widget.item.pk.toString(),
};
return PaginatedStockHistoryList(filters, showFilterOptions);
return PaginatedStockHistoryList(filters);
}
}
@ -45,8 +43,10 @@ class _StockItemHistoryDisplayState extends RefreshableState<StockItemHistoryWid
* Widget which displays a paginated stock history list
*/
class PaginatedStockHistoryList extends PaginatedSearchWidget {
const PaginatedStockHistoryList(Map<String, String> filters, bool showSearch)
: super(filters: filters, showSearch: showSearch);
const PaginatedStockHistoryList(Map<String, String> filters) : super(filters: filters);
@override
String get searchTitle => L10().stockItemHistory;
@override
_PaginatedStockHistoryState createState() => _PaginatedStockHistoryState();

View File

@ -1,5 +1,4 @@
import "package:flutter/material.dart";
import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:inventree/inventree/model.dart";
import "package:inventree/inventree/stock.dart";
@ -27,32 +26,21 @@ class _StockListState extends RefreshableState<StockItemList> {
final Map<String, String> filters;
bool showFilterOptions = false;
@override
String getAppBarTitle() => L10().stockItems;
@override
List<Widget> appBarActions(BuildContext context) => [
IconButton(
icon: FaIcon(FontAwesomeIcons.filter),
onPressed: () async {
setState(() {
showFilterOptions = !showFilterOptions;
});
},
)
];
@override
Widget getBody(BuildContext context) {
return PaginatedStockItemList(filters, showFilterOptions);
return PaginatedStockItemList(filters);
}
}
class PaginatedStockItemList extends PaginatedSearchWidget {
const PaginatedStockItemList(Map<String, String> filters, bool showSearch) : super(filters: filters, showSearch: showSearch);
const PaginatedStockItemList(Map<String, String> filters) : super(filters: filters);
@override
String get searchTitle => L10().stockItems;
@override
_PaginatedStockItemListState createState() => _PaginatedStockItemListState();

View File

@ -1,5 +1,4 @@
import "package:flutter/material.dart";
import "package:font_awesome_flutter/font_awesome_flutter.dart";
import "package:inventree/api.dart";
import "package:inventree/l10.dart";
@ -31,23 +30,9 @@ class _SupplierPartListState extends RefreshableState<SupplierPartList> {
@override
String getAppBarTitle() => L10().supplierParts;
bool showFilterOptions = false;
@override
List<Widget> appBarActions(BuildContext context) => [
IconButton(
icon: FaIcon(FontAwesomeIcons.filter),
onPressed: () async {
setState(() {
showFilterOptions = !showFilterOptions;
});
},
)
];
@override
Widget getBody(BuildContext context) {
return PaginatedSupplierPartList(widget.filters, showFilterOptions);
Widget getBody(BuildContext context) {
return PaginatedSupplierPartList(widget.filters);
}
}
@ -55,7 +40,10 @@ class _SupplierPartListState extends RefreshableState<SupplierPartList> {
class PaginatedSupplierPartList extends PaginatedSearchWidget {
const PaginatedSupplierPartList(Map<String, String> filters, bool showSearch) : super(filters: filters, showSearch: showSearch);
const PaginatedSupplierPartList(Map<String, String> filters) : super(filters: filters);
@override
String get searchTitle => L10().supplierParts;
@override
_PaginatedSupplierPartListState createState() => _PaginatedSupplierPartListState();