pretty nice property editor

This commit is contained in:
vabene1111 2023-12-02 17:41:02 +01:00
parent a3a2433d2a
commit fe11b88fd0
2 changed files with 81 additions and 60 deletions

View File

@ -612,10 +612,14 @@ class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
data = json.loads(response.content) data = json.loads(response.content)
food_property_list = [] food_property_list = []
food_property_types = food.foodproperty_set.values_list('property__property_type_id', flat=True)
for pt in PropertyType.objects.filter(space=request.space).all(): # delete all properties where the property type has a fdc_id as these should be overridden
if pt.fdc_id and pt.id not in food_property_types: for fp in food.properties.all():
if fp.property_type.fdc_id:
fp.delete()
for pt in PropertyType.objects.filter(space=request.space, fdc_id__gte=0).all():
if pt.fdc_id:
for fn in data['foodNutrients']: for fn in data['foodNutrients']:
if fn['nutrient']['id'] == pt.fdc_id: if fn['nutrient']['id'] == pt.fdc_id:
food_property_list.append(Property( food_property_list.append(Property(

View File

@ -4,48 +4,46 @@
<div> <div>
<h2 v-if="recipe">{{ recipe.name }}</h2> <h2 v-if="recipe">{{ recipe.name }}</h2>
<table class="table table-sm table-bordered"> <table class="table table-sm table-bordered table-responsive">
<thead> <thead>
<tr> <tr>
<td>{{ $t('Name') }}</td> <td>{{ $t('Name') }}</td>
<td>FDC</td> <td>FDC</td>
<td>{{ $t('Properties_Food_Amount') }}</td> <td>{{ $t('Properties_Food_Amount') }}</td>
<td>{{ $t('Properties_Food_Unit') }}</td> <td>{{ $t('Properties_Food_Unit') }}</td>
<td v-for="pt in properties" v-bind:key="pt.id"> <td v-for="pt in property_types" v-bind:key="pt.id">
<b-button variant="primary" @click="editing_property_type = pt" class="btn-block">{{ pt.name }} <span v-if="pt.unit !== ''">({{ pt.unit }})</span></b-button> <b-button variant="primary" @click="editing_property_type = pt" class="btn-block">{{ pt.name }} <span v-if="pt.unit !== ''">({{ pt.unit }})</span></b-button>
</td> </td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="f in this.foods" v-bind:key="f.food.id"> <tr v-for="f in this.foods" v-bind:key="f.id">
<td> <td>
{{ f.food.name }} {{ f.name }}
</td> </td>
<td> <td style="width: 11em;">
<b-input-group> <b-input-group>
<b-form-input v-model="f.food.fdc_id" type="number" @change="updateFood(f.food)" :disabled="loading"></b-form-input> <b-form-input v-model="f.fdc_id" type="number" @change="updateFood(f)" :disabled="f.loading"></b-form-input>
<b-input-group-append> <b-input-group-append>
<b-button variant="success" @click="updateFoodFromFDC(f.food)" :disabled="loading"><i class="fas fa-sync-alt" :class="{'fa-spin': loading}"></i></b-button> <b-button variant="success" @click="updateFoodFromFDC(f)" :disabled="f.loading"><i class="fas fa-sync-alt" :class="{'fa-spin': loading}"></i></b-button>
</b-input-group-append> </b-input-group-append>
</b-input-group> </b-input-group>
</td> </td>
<td> <td style="width: 5em; ">
<b-input v-model="f.food.properties_food_amount" type="number" @change="updateFood(f.food)" :disabled="loading"></b-input> <b-input v-model="f.properties_food_amount" type="number" @change="updateFood(f)" :disabled="f.loading"></b-input>
</td> </td>
<td> <td style="width: 8em;">
<generic-multiselect <generic-multiselect
@change="f.food.properties_food_unit = $event.val; updateFood(f.food)" @change="f.properties_food_unit = $event.val; updateFood(f)"
:initial_selection="f.food.properties_food_unit" :initial_single_selection="f.properties_food_unit"
label="name" :model="Models.UNIT" label="name" :model="Models.UNIT"
:multiple="false" :multiple="false"
:disabled="loading"/> :disabled="f.loading"/>
</td> </td>
<td v-for="p in f.properties" v-bind:key="`${f.id}_${p.property_type.id}`"> <td v-for="p in f.properties" v-bind:key="`${f.id}_${p.property_type.id}`">
<b-input-group> <b-input-group>
<b-form-input v-model="p.property_amount" type="number" :disabled="loading" ></b-form-input> <!-- TODO handle manual input --> <b-form-input v-model="p.property_amount" type="number" :disabled="f.loading" v-b-tooltip.focus :title="p.property_type.name" @change="updateFood(f)"></b-form-input>
</b-input-group> </b-input-group>
</td> </td>
</tr> </tr>
@ -57,7 +55,7 @@
:model="Models.PROPERTY_TYPE" :model="Models.PROPERTY_TYPE"
:action="Actions.UPDATE" :action="Actions.UPDATE"
:item1="editing_property_type" :item1="editing_property_type"
@finish-action="editing_property_type = null; loadPropertyTypes()"> @finish-action="editing_property_type = null; loadData()">
</generic-modal-form> </generic-modal-form>
</div> </div>
@ -86,32 +84,7 @@ export default {
mixins: [ApiMixin], mixins: [ApiMixin],
components: {GenericModalForm, GenericMultiselect}, components: {GenericModalForm, GenericMultiselect},
computed: { computed: {
foods: function () {
let foods = {}
if (this.recipe !== null && this.property_types !== []) {
this.recipe.steps.forEach(s => {
s.ingredients.forEach(i => {
let food = {food: i.food, properties: {}, loading: false}
this.property_types.forEach(pt => {
food.properties[pt.id] = {changed: false, property_amount: 0, property_type: pt}
})
i.food.properties.forEach(fp => {
food.properties[fp.property_type.id] = {changed: false, property_amount: fp.property_amount, property_type: fp.property_type}
})
foods[food.food.id] = food
})
})
}
return foods
},
properties: function () {
let properties = {}
this.property_types.forEach(pt => {
properties[pt.id] = pt
})
return properties
}
}, },
data() { data() {
return { return {
@ -119,51 +92,95 @@ export default {
property_types: [], property_types: [],
editing_property_type: null, editing_property_type: null,
loading: false, loading: false,
foods: [],
} }
}, },
mounted() { mounted() {
this.$i18n.locale = window.CUSTOM_LOCALE this.$i18n.locale = window.CUSTOM_LOCALE
this.loadRecipe(); this.loadData();
this.loadPropertyTypes();
}, },
methods: { methods: {
loadRecipe: function () { loadData: function () {
let apiClient = new ApiApiFactory() let apiClient = new ApiApiFactory()
apiClient.retrieveRecipe("112").then(result => { apiClient.listPropertyTypes().then(result => {
this.property_types = result.data
apiClient.retrieveRecipe("112").then(result => { //TODO get recipe id
this.recipe = result.data this.recipe = result.data
this.foods = []
this.recipe.steps.forEach(s => {
s.ingredients.forEach(i => {
if (this.foods.filter(x => (x.id === i.food.id)).length === 0) {
this.foods.push(this.buildFood(i.food))
}
})
})
this.loading = false; this.loading = false;
}).catch((err) => { }).catch((err) => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err) StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
}) })
},
loadPropertyTypes: function () {
let apiClient = new ApiApiFactory()
apiClient.listPropertyTypes().then(result => {
this.property_types = result.data
}).catch((err) => { }).catch((err) => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err) StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
}) })
}, },
buildFood: function (food) {
/**
* Prepare food for display in grid by making sure the food properties are in the same order as property_types and that no types are missing
* */
let existing_properties = {}
food.properties.forEach(fp => {
existing_properties[fp.property_type.id] = fp
})
let food_properties = []
this.property_types.forEach(pt => {
let new_food_property = {
property_type: pt,
property_amount: 0,
}
if (pt.id in existing_properties) {
new_food_property = existing_properties[pt.id]
}
food_properties.push(new_food_property)
})
this.$set(food, 'loading', false)
food.properties = food_properties
return food
},
spliceInFood: function (food){
/**
* replace food in foods list, for example after updates from the server
*/
this.foods = this.foods.map(f => (f.id === food.id) ? food : f)
},
updateFood: function (food) { updateFood: function (food) {
let apiClient = new ApiApiFactory() let apiClient = new ApiApiFactory()
apiClient.partialUpdateFood(food.id, food).then(result => { apiClient.partialUpdateFood(food.id, food).then(result => {
this.spliceInFood(this.buildFood(result.data))
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE) StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
}).catch((err) => { }).catch((err) => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err) StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
}) })
}, },
updateFoodFromFDC: function (food) { updateFoodFromFDC: function (food) {
this.loading = true; food.loading = true;
let apiClient = new ApiApiFactory() let apiClient = new ApiApiFactory()
apiClient.fdcFood(food.id).then(result => { apiClient.fdcFood(food.id).then(result => {
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE) StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
this.loadRecipe() this.spliceInFood(this.buildFood(result.data))
}).catch((err) => { }).catch((err) => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err) StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
this.loading = false; food.loading = false;
}) })
} }
}, },