diff --git a/lib/api_form.dart b/lib/api_form.dart index 6805f51a..947229a2 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -5,6 +5,7 @@ import 'package:dropdown_search/dropdown_search.dart'; import 'package:inventree/api.dart'; import 'package:inventree/app_colors.dart'; +import 'package:inventree/inventree/part.dart'; import 'package:inventree/widget/fields.dart'; import 'package:inventree/l10.dart'; @@ -31,9 +32,14 @@ class APIFormField { // JSON data which defines the field final dynamic data; + dynamic initial_data; + // Get the "api_url" associated with a related field String get api_url => data["api_url"] ?? ""; + // Get the "model" associated with a related field + String get model => data["model"] ?? ""; + // Is this field hidden? bool get hidden => (data['hidden'] ?? false) as bool; @@ -104,6 +110,36 @@ class APIFormField { String get placeholderText => (data['placeholder'] ?? '').toString(); + Future loadInitialData() async { + + // Only for "related fields" + if (type != "related field") { + return; + } + + // Null value? No point! + if (value == null) { + return; + } + + int? pk = int.tryParse(value.toString()); + + if (pk == null) { + return; + } + + String url = api_url + "/" + pk.toString() + "/"; + + final APIResponse response = await InvenTreeAPI().get( + url, + params: filters, + ); + + if (response.isValid()) { + initial_data = response.data; + } + } + // Construct a widget for this input Widget constructField() { switch (type) { @@ -132,6 +168,7 @@ class APIFormField { return DropdownSearch( mode: Mode.BOTTOM_SHEET, showSelectedItem: true, + selectedItem: initial_data, onFind: (String filter) async { Map _filters = {}; @@ -164,32 +201,29 @@ class APIFormField { }, label: label, hint: helpText, - onChanged: print, + onChanged: null, showClearButton: !required, - // popupTitle: Text( - // label, - // style: _labelStyle(), - // ), itemAsString: (dynamic item) { return item['pathstring']; }, + dropdownBuilder: (context, item, itemAsString) { + return _renderRelatedField(item, true, false); + }, popupItemBuilder: (context, item, isSelected) { - return ListTile( - title: Text( - item['pathstring'].toString(), - style: TextStyle(fontWeight: isSelected ? FontWeight.bold : FontWeight.normal), - ), - subtitle: Text(item['description'].toString()), - trailing: Text(item['pk'].toString()), - ); + return _renderRelatedField(item, isSelected, true); }, onSaved: (item) { - data['value'] = item['pk'].toString(); + if (item != null) { + data['value'] = item['pk'] ?? null; + } else { + data['value'] = null; + } }, isFilteredOnline: true, showSearchBox: true, autoFocusSearchBox: true, compareFn: (dynamic item, dynamic selectedItem) { + // Comparison is based on the PK value if (item == null || selectedItem == null) { return false; @@ -200,6 +234,48 @@ class APIFormField { ); } + Widget _renderRelatedField(dynamic item, bool selected, bool extended) { + // Render a "related field" based on the "model" type + + if (item == null) { + return Text( + helpText, + style: TextStyle( + fontStyle: FontStyle.italic + ), + ); + } + + switch (model) { + case "partcategory": + + var cat = InvenTreePartCategory.fromJson(item); + + return ListTile( + title: Text( + cat.pathstring, + style: TextStyle(fontWeight: selected && extended ? FontWeight.bold : FontWeight.normal) + ), + subtitle: extended ? Text( + cat.description, + style: TextStyle(fontWeight: selected ? FontWeight.bold : FontWeight.normal), + ) : null, + ); + default: + return ListTile( + title: Text( + "Unsupported model", + style: TextStyle( + fontWeight: FontWeight.bold, + color: COLOR_DANGER + ) + ), + subtitle: Text("Model '${model}' rendering not supported"), + ); + } + + } + // Construct a string input element Widget _constructString() { @@ -341,7 +417,6 @@ Future launchApiForm(BuildContext context, String title, String url, Map launchApiForm(BuildContext context, String title, String url, Map { } } - // TODO: Add a "Save" button - // widgets.add(Spacer()); - - /* - widgets.add( - TextButton( - child: Text( - L10().save - ), - onPressed: null, - ) - ); - */ - return widgets; } @@ -468,7 +534,14 @@ class _APIFormWidgetState extends State { Map _data = {}; for (var field in fields) { - _data[field.name] = field.value.toString(); + + dynamic value = field.value; + + if (value == null) { + _data[field.name] = ""; + } else { + _data[field.name] = value.toString(); + } } // TODO: Handle "POST" forms too!! diff --git a/lib/widget/category_display.dart b/lib/widget/category_display.dart index 62ac1ad7..4388cdd0 100644 --- a/lib/widget/category_display.dart +++ b/lib/widget/category_display.dart @@ -22,6 +22,8 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; +import '../api_form.dart'; + class CategoryDisplayWidget extends StatefulWidget { CategoryDisplayWidget(this.category, {Key? key}) : super(key: key); @@ -35,7 +37,6 @@ class CategoryDisplayWidget extends StatefulWidget { class _CategoryDisplayState extends RefreshableState { - final _editCategoryKey = GlobalKey(); @override String getAppBarTitle(BuildContext context) => L10().partCategory; @@ -71,7 +72,9 @@ class _CategoryDisplayState extends RefreshableState { IconButton( icon: FaIcon(FontAwesomeIcons.edit), tooltip: L10().edit, - onPressed: _editCategoryDialog, + onPressed: () { + _editCategoryDialog(context); + }, ) ); } @@ -80,49 +83,26 @@ class _CategoryDisplayState extends RefreshableState { } - void _editCategory(Map values) async { + void _editCategoryDialog(BuildContext context) { - final bool result = await category!.update(values: values); - - showSnackIcon( - result ? "Category edited" : "Category editing failed", - success: result - ); - - refresh(); - } - - void _editCategoryDialog() { + final _cat = category; // Cannot edit top-level category - if (category == null) { + if (_cat == null) { return; } - var _name; - var _description; - - showFormDialog( + launchApiForm( + context, L10().editCategory, - key: _editCategoryKey, - callback: () { - _editCategory({ - "name": _name, - "description": _description - }); + _cat.url, + { + "name": {}, + "description": {}, + "parent": {}, }, - fields: [ - StringField( - label: L10().name, - initial: category?.name, - onSaved: (value) => _name = value - ), - StringField( - label: L10().description, - initial: category?.description, - onSaved: (value) => _description = value - ) - ] + modelData: _cat.jsondata, + onSuccess: refresh, ); } diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart index 9aadcd79..032cdb2e 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part_detail.dart @@ -188,8 +188,6 @@ class _PartDisplayState extends RefreshableState { "link": {}, "category": { - "filters": { - } }, // Checkbox fields