diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index c65a551e..d894ae68 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -392,7 +392,7 @@ class InvenTreeModel { String search = params["search"] ?? ""; String original = params["original_search"] ?? ""; - params["search"] = "${search} ${original}"; + params["search"] = "${search} ${original}".trim(); params.remove("original_search"); } diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index 45606a2d..29391b26 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -149,7 +149,8 @@ class InvenTreeStockItem extends InvenTreeModel { "part_detail": "true", "location_detail": "true", "supplier_detail": "true", - "cascade": "false" + "cascade": "false", + "in_stock": "true", }; } diff --git a/lib/widget/location_display.dart b/lib/widget/location_display.dart index 26950a34..17ace857 100644 --- a/lib/widget/location_display.dart +++ b/lib/widget/location_display.dart @@ -16,6 +16,7 @@ import "package:flutter/material.dart"; import "package:flutter/foundation.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; +import 'package:inventree/widget/stock_list.dart'; class LocationDisplayWidget extends StatefulWidget { @@ -286,7 +287,7 @@ class _LocationDisplayState extends RefreshableState { children: detailTiles(), ); case 1: - return PaginatedStockList(filters); + return PaginatedStockItemList(filters); case 2: return ListView( children: ListTile.divideTiles( @@ -467,166 +468,3 @@ class SublocationList extends StatelessWidget { ); } } - -/* - * Widget for displaying a list of stock items within a stock location. - * - * Users server-side pagination for snappy results - */ - -class PaginatedStockList extends StatefulWidget { - - const PaginatedStockList(this.filters); - - final Map filters; - - @override - _PaginatedStockListState createState() => _PaginatedStockListState(filters); -} - - -class _PaginatedStockListState extends State { - - _PaginatedStockListState(this.filters); - - static const _pageSize = 25; - - String _searchTerm = ""; - - final Map filters; - - final PagingController _pagingController = PagingController(firstPageKey: 0); - - @override - void initState() { - _pagingController.addPageRequestListener((pageKey) { - _fetchPage(pageKey); - }); - - super.initState(); - } - - @override - void dispose() { - _pagingController.dispose(); - super.dispose(); - } - - int resultCount = 0; - - Future _fetchPage(int pageKey) async { - try { - - Map params = filters; - - params["search"] = "${_searchTerm}"; - - // Do we include stock items from sub-locations? - final bool cascade = await InvenTreeSettingsManager().getBool("stockSublocation", true); - - params["cascade"] = "${cascade}"; - - final page = await InvenTreeStockItem().listPaginated(_pageSize, pageKey, filters: params); - - int pageLength = page?.length ?? 0; - int pageCount = page?.count ?? 0; - - final isLastPage = pageLength < _pageSize; - - // Construct a list of stock item objects - List items = []; - - if (page != null) { - for (var result in page.results) { - if (result is InvenTreeStockItem) { - items.add(result); - } - } - } - - if (isLastPage) { - _pagingController.appendLastPage(items); - } else { - final int nextPageKey = pageKey + pageLength; - _pagingController.appendPage(items, nextPageKey); - } - - setState(() { - resultCount = pageCount; - }); - - } catch (error, stackTrace) { - _pagingController.error = error; - - sentryReportError(error, stackTrace); - } - } - - void _openItem(BuildContext context, int pk) { - InvenTreeStockItem().get(pk).then((var item) { - if (item is InvenTreeStockItem) { - Navigator.push(context, MaterialPageRoute(builder: (context) => StockDetailWidget(item))); - } - }); - } - - Widget _buildItem(BuildContext context, InvenTreeStockItem item) { - return ListTile( - title: Text("${item.partName}"), - subtitle: Text("${item.locationPathString}"), - leading: InvenTreeAPI().getImage( - item.partThumbnail, - width: 40, - height: 40, - ), - trailing: Text("${item.displayQuantity}", - style: TextStyle( - fontWeight: FontWeight.bold, - color: item.statusColor, - ), - ), - onTap: () { - _openItem(context, item.pk); - }, - ); - } - - final TextEditingController searchController = TextEditingController(); - - void updateSearchTerm() { - _searchTerm = searchController.text; - _pagingController.refresh(); - } - - @override - Widget build (BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - PaginatedSearchWidget(searchController, updateSearchTerm, resultCount), - Expanded( - child: CustomScrollView( - shrinkWrap: true, - physics: ClampingScrollPhysics(), - scrollDirection: Axis.vertical, - slivers: [ - // TODO - Search input - PagedSliverList.separated( - pagingController: _pagingController, - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) { - return _buildItem(context, item); - }, - noItemsFoundIndicatorBuilder: (context) { - return NoResultsWidget("No stock items found"); - } - ), - separatorBuilder: (context, item) => const Divider(height: 1), - ) - ] - ) - ) - ] - ); - } -} diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart index 54d2e4c5..254e1a53 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part_detail.dart @@ -18,6 +18,7 @@ import "package:inventree/widget/part_image_widget.dart"; import "package:inventree/widget/stock_detail.dart"; import "package:inventree/widget/location_display.dart"; +import 'package:inventree/widget/stock_list.dart'; class PartDetailWidget extends StatefulWidget { @@ -494,7 +495,9 @@ class _PartDisplayState extends RefreshableState { ), ); case 1: - return PaginatedStockList({"part": "${part.pk}"}); + return PaginatedStockItemList( + {"part": "${part.pk}"} + ); case 2: return Center( child: ListView( diff --git a/lib/widget/purchase_order_detail.dart b/lib/widget/purchase_order_detail.dart index 9a5cd703..4275bb89 100644 --- a/lib/widget/purchase_order_detail.dart +++ b/lib/widget/purchase_order_detail.dart @@ -13,6 +13,7 @@ import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/l10.dart"; import "package:inventree/widget/location_display.dart"; import "package:inventree/widget/snacks.dart"; +import 'package:inventree/widget/stock_list.dart'; import "package:one_context/one_context.dart"; @@ -351,7 +352,7 @@ class _PurchaseOrderDetailState extends RefreshableState filters; @@ -55,7 +55,7 @@ class _PaginatedPurchaseOrderList extends StatefulWidget { } -class _PaginatedPurchaseOrderListState extends State<_PaginatedPurchaseOrderList> { +class _PaginatedPurchaseOrderListState extends State { _PaginatedPurchaseOrderListState(this.filters, this.onTotalChanged); diff --git a/lib/widget/search.dart b/lib/widget/search.dart index ca838dcd..c522c747 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -4,6 +4,7 @@ import 'package:inventree/inventree/company.dart'; import 'package:inventree/inventree/purchase_order.dart'; import "package:inventree/widget/part_detail.dart"; import "package:inventree/widget/progress.dart"; +import 'package:inventree/widget/purchase_order_list.dart'; import 'package:inventree/widget/refreshable_state.dart'; import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/stock_detail.dart"; @@ -13,6 +14,9 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:inventree/l10.dart"; import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/stock.dart"; +import 'package:inventree/widget/stock_list.dart'; + +import 'company_list.dart'; // Widget for performing database-wide search @@ -204,6 +208,18 @@ class _SearchDisplayState extends RefreshableState { title: Text(L10().stockItems), leading: FaIcon(FontAwesomeIcons.boxes), trailing: Text("${nStockResults}"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StockItemList( + { + "original_search": query, + } + ) + ) + ); + }, ) ); } @@ -251,6 +267,18 @@ class _SearchDisplayState extends RefreshableState { title: Text(L10().purchaseOrders), leading: FaIcon(FontAwesomeIcons.shoppingCart), trailing: Text("${nPurchaseOrderResults}"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PurchaseOrderListWidget( + filters: { + "original_search": query + } + ) + ) + ); + }, ) ); } diff --git a/lib/widget/stock_list.dart b/lib/widget/stock_list.dart new file mode 100644 index 00000000..f3affc50 --- /dev/null +++ b/lib/widget/stock_list.dart @@ -0,0 +1,201 @@ + +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; +import "package:inventree/inventree/sentry.dart"; +import "package:inventree/inventree/stock.dart"; +import "package:inventree/widget/paginator.dart"; +import "package:inventree/widget/refreshable_state.dart"; + +import "package:inventree/l10.dart"; +import "package:inventree/app_settings.dart"; +import "package:inventree/widget/stock_detail.dart"; + +import "package:inventree/api.dart"; + +class StockItemList extends StatefulWidget { + + const StockItemList(this.filters); + + final Map filters; + + @override + _StockListState createState() => _StockListState(filters); +} + + +class _StockListState extends RefreshableState { + + _StockListState(this.filters); + + final Map filters; + + @override + String getAppBarTitle(BuildContext context) => L10().purchaseOrders; + + @override + Widget getBody(BuildContext context) { + return PaginatedStockItemList(filters); + } +} + +class PaginatedStockItemList extends StatefulWidget { + + const PaginatedStockItemList(this.filters); + + final Map filters; + + @override + _PaginatedStockItemListState createState() => _PaginatedStockItemListState(filters); + +} + + +class _PaginatedStockItemListState extends State { + + _PaginatedStockItemListState(this.filters); + + static const _pageSize = 25; + + String _searchTerm = ""; + + final Map filters; + + final PagingController _pagingController = PagingController(firstPageKey: 0); + + @override + String getAppbarTitle(BuildContext context) => L10().stockItems; + + @override + void initState() { + _pagingController.addPageRequestListener((pageKey) { + _fetchPage(pageKey); + }); + + super.initState(); + } + + @override + void dispose() { + _pagingController.dispose(); + super.dispose(); + } + + int resultCount = 0; + + Future _fetchPage(int pageKey) async { + try { + + Map params = filters; + + params["search"] = "${_searchTerm}"; + + // Do we include stock items from sub-locations? + final bool cascade = await InvenTreeSettingsManager().getBool("stockSublocation", true); + + params["cascade"] = "${cascade}"; + + final page = await InvenTreeStockItem().listPaginated(_pageSize, pageKey, filters: params); + + int pageLength = page?.length ?? 0; + int pageCount = page?.count ?? 0; + + final isLastPage = pageLength < _pageSize; + + // Construct a list of stock item objects + List items = []; + + if (page != null) { + for (var result in page.results) { + if (result is InvenTreeStockItem) { + items.add(result); + } + } + } + + if (isLastPage) { + _pagingController.appendLastPage(items); + } else { + final int nextPageKey = pageKey + pageLength; + _pagingController.appendPage(items, nextPageKey); + } + + setState(() { + resultCount = pageCount; + }); + + } catch (error, stackTrace) { + _pagingController.error = error; + + sentryReportError(error, stackTrace); + } + } + + void _openItem(BuildContext context, int pk) { + InvenTreeStockItem().get(pk).then((var item) { + if (item is InvenTreeStockItem) { + Navigator.push(context, MaterialPageRoute(builder: (context) => StockDetailWidget(item))); + } + }); + } + + Widget _buildItem(BuildContext context, InvenTreeStockItem item) { + return ListTile( + title: Text("${item.partName}"), + subtitle: Text("${item.locationPathString}"), + leading: InvenTreeAPI().getImage( + item.partThumbnail, + width: 40, + height: 40, + ), + trailing: Text("${item.displayQuantity}", + style: TextStyle( + fontWeight: FontWeight.bold, + color: item.statusColor, + ), + ), + onTap: () { + _openItem(context, item.pk); + }, + ); + } + + final TextEditingController searchController = TextEditingController(); + + void updateSearchTerm() { + _searchTerm = searchController.text; + _pagingController.refresh(); + } + + @override + Widget build (BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + PaginatedSearchWidget(searchController, updateSearchTerm, resultCount), + Expanded( + child: CustomScrollView( + shrinkWrap: true, + physics: ClampingScrollPhysics(), + scrollDirection: Axis.vertical, + slivers: [ + // TODO - Search input + PagedSliverList.separated( + pagingController: _pagingController, + builderDelegate: PagedChildBuilderDelegate( + itemBuilder: (context, item, index) { + return _buildItem(context, item); + }, + noItemsFoundIndicatorBuilder: (context) { + return NoResultsWidget("No stock items found"); + } + ), + separatorBuilder: (context, item) => const Divider(height: 1), + ) + ] + ) + ) + ] + ); + } +} \ No newline at end of file