mirror of
https://github.com/inventree/inventree-app.git
synced 2025-04-28 13:36:50 +00:00
API form for editing PartCategory
- Custom related field renderer function - Grab related field data from the server
This commit is contained in:
parent
377da3c2fb
commit
bc713dfdcd
@ -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<void> 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<dynamic>(
|
||||
mode: Mode.BOTTOM_SHEET,
|
||||
showSelectedItem: true,
|
||||
selectedItem: initial_data,
|
||||
onFind: (String filter) async {
|
||||
|
||||
Map<String, String> _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<void> launchApiForm(BuildContext context, String title, String url, Map<S
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Custom filter updating
|
||||
} else {
|
||||
remoteField[key] = localField[key];
|
||||
}
|
||||
@ -360,6 +435,11 @@ Future<void> launchApiForm(BuildContext context, String title, String url, Map<S
|
||||
formFields.add(APIFormField(fieldName, remoteField));
|
||||
}
|
||||
|
||||
// Grab existing data for each form field
|
||||
for (var field in formFields) {
|
||||
await field.loadInitialData();
|
||||
}
|
||||
|
||||
// Now, launch a new widget!
|
||||
Navigator.push(
|
||||
context,
|
||||
@ -445,20 +525,6 @@ class _APIFormWidgetState extends State<APIFormWidget> {
|
||||
}
|
||||
}
|
||||
|
||||
// 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<APIFormWidget> {
|
||||
Map<String, String> _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!!
|
||||
|
@ -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<CategoryDisplayWidget> {
|
||||
|
||||
final _editCategoryKey = GlobalKey<FormState>();
|
||||
|
||||
@override
|
||||
String getAppBarTitle(BuildContext context) => L10().partCategory;
|
||||
@ -71,7 +72,9 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
|
||||
IconButton(
|
||||
icon: FaIcon(FontAwesomeIcons.edit),
|
||||
tooltip: L10().edit,
|
||||
onPressed: _editCategoryDialog,
|
||||
onPressed: () {
|
||||
_editCategoryDialog(context);
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -80,49 +83,26 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
|
||||
|
||||
}
|
||||
|
||||
void _editCategory(Map<String, String> 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: <Widget>[
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -188,8 +188,6 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
||||
"link": {},
|
||||
|
||||
"category": {
|
||||
"filters": {
|
||||
}
|
||||
},
|
||||
|
||||
// Checkbox fields
|
||||
|
Loading…
x
Reference in New Issue
Block a user