diff --git a/assets/images/svg/bug.svg b/assets/images/svg/bug.svg new file mode 100644 index 0000000..a54a5e1 --- /dev/null +++ b/assets/images/svg/bug.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/categories.svg b/assets/images/svg/categories.svg new file mode 100644 index 0000000..dd55441 --- /dev/null +++ b/assets/images/svg/categories.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/svg/contragents.svg b/assets/images/svg/contragents.svg new file mode 100644 index 0000000..653fa83 --- /dev/null +++ b/assets/images/svg/contragents.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/svg/global.svg b/assets/images/svg/global.svg new file mode 100644 index 0000000..6ae3271 --- /dev/null +++ b/assets/images/svg/global.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/svg/goods.svg b/assets/images/svg/goods.svg new file mode 100644 index 0000000..16d55e1 --- /dev/null +++ b/assets/images/svg/goods.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/images/svg/inventarization.svg b/assets/images/svg/inventarization.svg new file mode 100644 index 0000000..91e79e7 --- /dev/null +++ b/assets/images/svg/inventarization.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/images/svg/logout.svg b/assets/images/svg/logout.svg new file mode 100644 index 0000000..ebbb408 --- /dev/null +++ b/assets/images/svg/logout.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/options.svg b/assets/images/svg/options.svg new file mode 100644 index 0000000..0074666 --- /dev/null +++ b/assets/images/svg/options.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/svg/question.svg b/assets/images/svg/question.svg new file mode 100644 index 0000000..10637cc --- /dev/null +++ b/assets/images/svg/question.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/settings.svg b/assets/images/svg/settings.svg new file mode 100644 index 0000000..e6c00f4 --- /dev/null +++ b/assets/images/svg/settings.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/routes/route_names.dart b/lib/routes/route_names.dart index d47608a..377d7fd 100644 --- a/lib/routes/route_names.dart +++ b/lib/routes/route_names.dart @@ -13,4 +13,8 @@ const String settingPrinterBluetoothViewRoute = 'SettingPrinterBluetoothView'; const String categoryEditRoute = 'categoryEditRoute'; -const String categorySelectViewRoute = 'categorySelectViewRoute'; \ No newline at end of file +const String categorySelectViewRoute = 'categorySelectViewRoute'; + +const String goodsEditRoute = 'goodsEditRoute'; +const String goodsDictionaryViewRoute = 'goodsDictionaryViewRoute'; + diff --git a/lib/routes/router.dart b/lib/routes/router.dart index f4632b1..8664945 100644 --- a/lib/routes/router.dart +++ b/lib/routes/router.dart @@ -2,6 +2,7 @@ import 'package:satu/views/dictionaries/category/category_edit.dart'; import 'package:satu/views/dictionaries/category/category_select_view.dart'; import 'package:satu/views/dictionaries/category/category_view.dart'; +import 'package:satu/views/dictionaries/goods/goods_view.dart'; import 'package:satu/views/work/views/add_by_barcode/add_by_barcode_view.dart'; import 'package:satu/views/work/views/add_product/add_product_view.dart'; import 'package:satu/views/login/login_view.dart'; @@ -68,6 +69,11 @@ Route generateRoute(RouteSettings settings) { routeName: settings.name!, viewToShow: CategorySelectView(), ); + case goodsDictionaryViewRoute: + return _getPageRoute( + routeName: settings.name!, + viewToShow: GoodsDictionaryView(), + ); // case ImageShowRoute: // ImageShowModel data = settings.arguments as ImageShowModel; diff --git a/lib/views/dictionaries/category/category_select_view.dart b/lib/views/dictionaries/category/category_select_view.dart index 04bd12a..8109b9b 100644 --- a/lib/views/dictionaries/category/category_select_view.dart +++ b/lib/views/dictionaries/category/category_select_view.dart @@ -91,12 +91,23 @@ class _CategorySelectViewState extends State { itemBuilder: (BuildContext context, int index) { final CategoryRowDao category = items[index]; return DictionaryTile( - title: category.name, - subTitle: category.parentName.isEmpty - ? 'Корневая категория' - : 'Родитель: ${category.parentName}', key: Key('category_${category.id}'), onPress: () => handlerCategory(category.id!), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + category.name, + style: const TextStyle(fontSize: 12, color: textColor), + ), + Text( + category.parentName.isEmpty + ? 'Корневая категория' + : 'Родитель: ${category.parentName}', + style: const TextStyle( + fontSize: 10, color: placeholderColor)), + ], + ), ); }, separatorBuilder: (BuildContext context, int index) { diff --git a/lib/views/dictionaries/category/category_view.dart b/lib/views/dictionaries/category/category_view.dart index 5d7f77c..57e497a 100644 --- a/lib/views/dictionaries/category/category_view.dart +++ b/lib/views/dictionaries/category/category_view.dart @@ -70,6 +70,7 @@ class _CategoryDictionaryViewState extends State { controller: _searchTextController, fieldFocusNode: _searchFocusNode, ), + const ProductsTitleBarBar(title: 'Список категории'), Expanded( child: ListView.separated( physics: const BouncingScrollPhysics(), @@ -77,13 +78,24 @@ class _CategoryDictionaryViewState extends State { itemBuilder: (BuildContext context, int index) { final CategoryRowDao category = items[index]; return DictionaryTile( - title: category.name, - subTitle: category.parentName.isEmpty - ? 'Корневая категория' - : 'Родитель: ${category.parentName}', key: Key('category_${category.id}'), onPress: () => _navigatorService.push(categoryEditRoute, arguments: category), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + category.name, + style: const TextStyle(fontSize: 12, color: textColor), + ), + Text( + category.parentName.isEmpty + ? 'Корневая категория' + : 'Родитель: ${category.parentName}', + style: const TextStyle( + fontSize: 10, color: placeholderColor)), + ], + ), ); }, separatorBuilder: (BuildContext context, int index) { @@ -139,7 +151,7 @@ class _CategoryDictionaryViewState extends State { } class CategoryRowDao { - CategoryRowDao(this.name, this.parentName, this.id, { this.parentId = 0}); + CategoryRowDao(this.name, this.parentName, this.id, {this.parentId = 0}); final String name; final String parentName; diff --git a/lib/views/dictionaries/component/dictionary_list_tile.dart b/lib/views/dictionaries/component/dictionary_list_tile.dart index 81eb705..7da16c5 100644 --- a/lib/views/dictionaries/component/dictionary_list_tile.dart +++ b/lib/views/dictionaries/component/dictionary_list_tile.dart @@ -3,13 +3,13 @@ import 'package:satu/shared/app_colors.dart'; class DictionaryTile extends StatelessWidget { const DictionaryTile({ - required this.title, + required this.child, Key? key, this.subTitle, this.onPress, }) : super(key: key); - final String title; + final Widget child; final String? subTitle; final Function()? onPress; @@ -24,19 +24,7 @@ class DictionaryTile extends StatelessWidget { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: const TextStyle(fontSize: 12, color: textColor), - ), - if (subTitle != null && subTitle!.isNotEmpty) - Text(subTitle!, - style: const TextStyle( - fontSize: 10, color: placeholderColor)), - ], - ), + child: child, ), ), ), diff --git a/lib/views/dictionaries/goods/goods_view.dart b/lib/views/dictionaries/goods/goods_view.dart new file mode 100644 index 0000000..18428d5 --- /dev/null +++ b/lib/views/dictionaries/goods/goods_view.dart @@ -0,0 +1,168 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:satu/core/entity/category_entity.dart'; +import 'package:satu/core/entity/goods_entity.dart'; +import 'package:satu/core/redux/actions/sell_actions.dart'; +import 'package:satu/core/services/dictionary_service.dart'; +import 'package:satu/core/services/navigator_service.dart'; +import 'package:satu/core/utils/locator.dart'; +import 'package:satu/routes/route_names.dart'; +import 'package:satu/shared/app_colors.dart'; +import 'package:satu/views/dictionaries/component/dictionary_list_tile.dart'; +import 'package:satu/widgets/bar/products_app_bar.dart'; +import 'package:satu/widgets/bar/products_title_bar.dart'; +import 'package:satu/widgets/fields/input_field.dart'; +import 'package:satu/widgets/ui/product_title_widget.dart'; + +class GoodsDictionaryView extends StatefulWidget { + @override + _GoodsDictionaryViewState createState() => _GoodsDictionaryViewState(); +} + +class _GoodsDictionaryViewState extends State { + final DictionaryService _dictionaryService = locator(); + final NavigatorService _navigatorService = locator(); + late TextEditingController _searchTextController; + final FocusNode _searchFocusNode = FocusNode(); + + late List _goods = []; + late List _categories = []; + late List items = []; + + @override + void initState() { + _searchTextController = TextEditingController(); + _searchTextController.addListener(() { + if (_searchTextController.text.isNotEmpty) { + searchByField(_searchTextController.text); + } else { + reset(); + } + }); + initQuery(); + super.initState(); + } + + Future initQuery() async { + _goods = await _dictionaryService.getGoodsByNameOrEan(''); + _categories = await _dictionaryService.getCategoriesAll(); + searchByField(''); + } + + @override + void dispose() { + _searchTextController.dispose(); + _searchFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: ProductsAppBar( + title: 'Товары', + drawerShow: true, + actions: [ + ClipRRect( + borderRadius: BorderRadius.circular(90), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: (){}, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: SvgPicture.asset( + 'assets/images/svg/options.svg', + height: 20, + width: 20, + color: textColor, + ), + ), + ), + ), + ), + ], + ), + body: Column( + children: [ + InputField( + placeholder: 'Поиск по наименованию товара или штрих-код', + search: true, + controller: _searchTextController, + fieldFocusNode: _searchFocusNode, + ), + const ProductsTitleBarBar(title: 'Список товаров'), + Expanded( + child: ListView.separated( + physics: const BouncingScrollPhysics(), + itemCount: items.length, + itemBuilder: (BuildContext context, int index) { + final GoodRowDao good = items[index]; + return DictionaryTile( + child: ProductTitleWidget( + name: good.name, + categoryName: good.category, + ean: good.ean, + ), + ); + }, + separatorBuilder: (BuildContext context, int index) { + return const Divider( + height: 1.0, + color: disableColor, + ); + }, + ), + ), + ], + ), + floatingActionButton: FloatingActionButton( + elevation: 2, + onPressed: () => locator().push(categoryEditRoute), + child: const Icon( + Icons.add_rounded, + size: 34.0, + color: whiteColor, + ), + ), + ); + } + + void reset() { + _searchTextController.clear(); + searchByField(''); + } + + Future searchByField(String query) async { + log.i(query); + final List list = []; + final Iterable filtered = query == '' + ? _goods + : _goods.where((element) => + element.name.toLowerCase().contains(query.toLowerCase()) || + (element.ean != null && + element.ean!.contains(query.toLowerCase()))); + filtered.forEach((element) { + final Category category = _categories + .firstWhere((parent) => parent.id == element.categoryId, orElse: () { + return Category(); + }); + final String parentName = category.name; + final GoodRowDao rowDao = GoodRowDao(element.name, parentName, + ean: element.ean, id: element.id); + list.add(rowDao); + }); + setState(() { + items = list; + }); + } +} + +class GoodRowDao { + GoodRowDao(this.name, this.category, {this.ean, this.id}); + + final String name; + final String category; + final String? ean; + final int? id; +} diff --git a/lib/views/login/login_view.dart b/lib/views/login/login_view.dart index 3c4f719..6d6852c 100644 --- a/lib/views/login/login_view.dart +++ b/lib/views/login/login_view.dart @@ -57,44 +57,47 @@ class _LoginViewState extends State { converter: (store) => store.state.userState!, builder: (context, vm) { return Scaffold( - body: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - LogoSatu(), - InputField( - placeholder: 'Введите почту', - controller: emailController, - textInputType: TextInputType.emailAddress, - nextFocusNode: passwordNode, - ), - verticalSpaceSmall, - InputField( - placeholder: 'Введите пароль', - password: true, - controller: passwordController, - fieldFocusNode: passwordNode, - enterPressed: _pressBtnEnter, - textInputAction: TextInputAction.done, - ), - verticalSpaceMedium, - Padding( - padding: EdgeInsets.only( left: 45.sp, right: 45.sp, top: 30.sp ), - child: BusyButton( - title: 'ВОЙТИ', - busy: vm.isLoading!, - onPressed: _pressBtnEnter, + body: SingleChildScrollView( + physics: BouncingScrollPhysics(), + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + LogoSatu(), + InputField( + placeholder: 'Введите почту', + controller: emailController, + textInputType: TextInputType.emailAddress, + nextFocusNode: passwordNode, ), - ), - verticalSpaceLarge, - IconButton( - icon: Icon(MdiIcons.qrcodeScan), - iconSize: ScreenUtil().setSp(40.0), - tooltip: "Scan", - onPressed: scan, - ) - ], + verticalSpaceSmall, + InputField( + placeholder: 'Введите пароль', + password: true, + controller: passwordController, + fieldFocusNode: passwordNode, + enterPressed: _pressBtnEnter, + textInputAction: TextInputAction.done, + ), + verticalSpaceMedium, + Padding( + padding: EdgeInsets.only( left: 45.sp, right: 45.sp, top: 30.sp ), + child: BusyButton( + title: 'ВОЙТИ', + busy: vm.isLoading!, + onPressed: _pressBtnEnter, + ), + ), + verticalSpaceLarge, + IconButton( + icon: Icon(MdiIcons.qrcodeScan), + iconSize: ScreenUtil().setSp(40.0), + tooltip: "Scan", + onPressed: scan, + ) + ], + ), )); }); } diff --git a/lib/views/main/main_view.dart b/lib/views/main/main_view.dart index a3f46bb..3866dce 100644 --- a/lib/views/main/main_view.dart +++ b/lib/views/main/main_view.dart @@ -5,6 +5,7 @@ import 'package:satu/core/redux/store.dart'; import 'package:satu/core/services/navigator_service.dart'; import 'package:satu/core/utils/locator.dart'; import 'package:satu/views/dictionaries/category/category_view.dart'; +import 'package:satu/views/dictionaries/goods/goods_view.dart'; import 'package:satu/views/settings/printer_bluetooth/PrinterSelect.dart'; import 'package:satu/views/settings/setting_view.dart'; import 'package:satu/views/work/work_view.dart'; @@ -22,6 +23,7 @@ class _MainViewState extends State { final _workView = const WorkView(); final _settingsView = SettingsView(); final _categoryDictView = CategoryDictionaryView(); + final _goodDictView = GoodsDictionaryView(); Widget _body(Type viewClass) { if(viewClass == WorkView) { @@ -33,6 +35,9 @@ class _MainViewState extends State { if(viewClass == CategoryDictionaryView) { return _categoryDictView; } + if(viewClass == GoodsDictionaryView) { + return _goodDictView; + } return _workView; } diff --git a/lib/widgets/drawer/app_drawer.dart b/lib/widgets/drawer/app_drawer.dart index 58b9673..25dad1b 100644 --- a/lib/widgets/drawer/app_drawer.dart +++ b/lib/widgets/drawer/app_drawer.dart @@ -1,6 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:satu/core/redux/actions/nav_actions.dart'; import 'package:satu/core/redux/actions/user_actions.dart'; import 'package:satu/core/redux/store.dart'; @@ -8,7 +9,7 @@ import 'package:satu/core/redux/store.dart'; import 'package:satu/shared/app_colors.dart'; import 'package:satu/shared/ui_helpers.dart'; import 'package:satu/views/dictionaries/category/category_view.dart'; -import 'package:satu/views/settings/setting_view.dart'; +import 'package:satu/views/dictionaries/goods/goods_view.dart'; import 'package:satu/views/work/work_view.dart'; class AppDrawer extends StatelessWidget { @@ -23,31 +24,38 @@ class AppDrawer extends StatelessWidget { _createHeader(), _createDrawerSectionTitle(text: 'ОСНОВНОЙ РАЗДЕЛ'), _createDrawerItem( - icon: Icons.campaign_sharp, + svgFile: 'sell', text: 'Касса', onTap: () { Navigator.of(context).pop(); Redux.store!.dispatch(navigateDrawer(WorkView)); }), - _createDrawerItem(icon: Icons.check, text: 'Инвентаризация'), + _createDrawerItem( + svgFile: 'inventarization', text: 'Инвентаризация'), _createDrawerSectionTitle(text: 'СПРАВОЧНИКИ'), _createDrawerItem( - icon: Icons.list, + svgFile: 'categories', text: 'Категории', onTap: () { Navigator.of(context).pop(); Redux.store!.dispatch(navigateDrawer(CategoryDictionaryView)); }), _createDrawerItem( - icon: Icons.production_quantity_limits, text: 'Товары'), - _createDrawerItem(icon: Icons.people, text: 'Контрагенты'), + svgFile: 'goods', + text: 'Товары', + onTap: () { + Navigator.of(context).pop(); + Redux.store!.dispatch(navigateDrawer(GoodsDictionaryView)); + }), + _createDrawerItem(svgFile: 'contragents', text: 'Контрагенты'), _createDrawerSectionTitle(text: 'ИНФОРМАЦИЯ'), - _createDrawerItem(icon: Icons.question_answer, text: 'Справочник'), + _createDrawerItem(svgFile: 'question', text: 'Справочник'), _createDrawerSectionTitle(text: 'ПРОЧЕЕ'), - _createDrawerItem(icon: Icons.settings, text: 'Настройки'), - _createDrawerItem(icon: Icons.next_plan, text: 'Перейти на сайт'), + _createDrawerItem(svgFile: 'settings', text: 'Настройки'), + _createDrawerItem(svgFile: 'global', text: 'Перейти на сайт'), + _createDrawerItem(svgFile: 'bug', text: 'Сообщить об ошибке'), _createDrawerItem( - icon: Icons.exit_to_app, + svgFile: 'logout', text: 'Выйти из аккаунта', isDanger: true, onTap: () async { @@ -102,8 +110,9 @@ class AppDrawer extends StatelessWidget { } Widget _createDrawerItem( - {required IconData icon, - required String text, + {required String text, + IconData? icon, + String? svgFile, GestureTapCallback? onTap, bool isDanger = false}) { return Container( @@ -117,13 +126,21 @@ class AppDrawer extends StatelessWidget { const EdgeInsets.symmetric(vertical: 15.0, horizontal: 20.0), child: Row( children: [ - Icon( - icon, - size: 20.0, - color: isDanger ? dangerColor : textColor, - ), + if (svgFile != null) + SvgPicture.asset( + 'assets/images/svg/$svgFile.svg', + height: 20, + width: 20, + color: isDanger ? dangerColor : textColor, + ), + if (icon != null) + Icon( + icon, + size: 20.0, + color: isDanger ? dangerColor : textColor, + ), Padding( - padding: EdgeInsets.only(left: 8.0), + padding: const EdgeInsets.only(left: 8.0), child: Text( text, style: TextStyle(