shopping_list_category
This commit is contained in:
parent
22bde0424c
commit
85aca8acf6
@ -276,8 +276,9 @@ class UnitSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
||||
class SupermarketCategorySerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
|
||||
|
||||
def create(self, validated_data):
|
||||
obj, created = SupermarketCategory.objects.get_or_create(name=validated_data['name'],
|
||||
space=self.context['request'].space)
|
||||
validated_data['name'] = validated_data['name'].strip()
|
||||
validated_data['space'] = self.context['request'].space
|
||||
obj, created = SupermarketCategory.objects.get_or_create(**validated_data)
|
||||
return obj
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
@ -285,7 +286,7 @@ class SupermarketCategorySerializer(UniqueFieldsMixin, WritableNestedModelSerial
|
||||
|
||||
class Meta:
|
||||
model = SupermarketCategory
|
||||
fields = ('id', 'name')
|
||||
fields = ('id', 'name', 'description')
|
||||
|
||||
|
||||
class SupermarketCategoryRelationSerializer(WritableNestedModelSerializer):
|
||||
@ -301,7 +302,7 @@ class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Supermarket
|
||||
fields = ('id', 'name', 'category_to_supermarket')
|
||||
fields = ('id', 'name', 'description', 'category_to_supermarket')
|
||||
|
||||
|
||||
class RecipeSimpleSerializer(serializers.ModelSerializer):
|
||||
|
@ -10,7 +10,7 @@ from cookbook.helper import dal
|
||||
|
||||
from .models import (Comment, Food, InviteLink, Keyword, MealPlan, Recipe,
|
||||
RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList,
|
||||
Storage, Sync, SyncLog, Unit, get_model_name)
|
||||
Storage, Supermarket, SupermarketCategory, Sync, SyncLog, Unit, get_model_name)
|
||||
from .views import api, data, delete, edit, import_export, lists, new, views, telegram
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
@ -176,7 +176,7 @@ for m in generic_models:
|
||||
)
|
||||
)
|
||||
|
||||
vue_models = [Food, Keyword, Unit]
|
||||
vue_models = [Food, Keyword, Unit, Supermarket, SupermarketCategory]
|
||||
for m in vue_models:
|
||||
py_name = get_model_name(m)
|
||||
url_name = py_name.replace('_', '-')
|
||||
|
@ -359,7 +359,7 @@ class SupermarketCategoryRelationViewSet(viewsets.ModelViewSet, StandardFilterMi
|
||||
pagination_class = DefaultPagination
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = self.queryset.filter(supermarket__space=self.request.space)
|
||||
self.queryset = self.queryset.filter(supermarket__space=self.request.space).order_by('order')
|
||||
return super().get_queryset()
|
||||
|
||||
|
||||
|
@ -146,7 +146,39 @@ def unit(request):
|
||||
"title": _("Units"),
|
||||
"config": {
|
||||
'model': "UNIT", # *REQUIRED* name of the model in models.js
|
||||
'recipe_param': 'units' # *OPTIONAL* name of the listRecipes parameter if filtering on this attribute
|
||||
'recipe_param': 'units', # *OPTIONAL* name of the listRecipes parameter if filtering on this attribute
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@group_required('user')
|
||||
def supermarket(request):
|
||||
# recipe-param is the name of the parameters used when filtering recipes by this attribute
|
||||
# model-name is the models.js name of the model, probably ALL-CAPS
|
||||
return render(
|
||||
request,
|
||||
'generic/model_template.html',
|
||||
{
|
||||
"title": _("Supermarkets"),
|
||||
"config": {
|
||||
'model': "SUPERMARKET", # *REQUIRED* name of the model in models.js
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@group_required('user')
|
||||
def supermarket_category(request):
|
||||
# recipe-param is the name of the parameters used when filtering recipes by this attribute
|
||||
# model-name is the models.js name of the model, probably ALL-CAPS
|
||||
return render(
|
||||
request,
|
||||
'generic/model_template.html',
|
||||
{
|
||||
"title": _("Shopping Categories"),
|
||||
"config": {
|
||||
'model': "SHOPPING_CATEGORY", # *REQUIRED* name of the model in models.js
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -1,37 +1,80 @@
|
||||
<template>
|
||||
<div id="app" style="margin-bottom: 4vh">
|
||||
<!-- v-if prevents component from loading before this_model has been assigned -->
|
||||
<div id="app" style="margin-bottom: 4vh" v-if="this_model">
|
||||
<generic-modal-form v-if="this_model"
|
||||
:model="this_model"
|
||||
:action="this_action"
|
||||
:item1="this_item"
|
||||
:item2="this_target"
|
||||
:show="show_modal"
|
||||
@finish-action="finishAction"/>
|
||||
<generic-split-lists v-if="this_model"
|
||||
:list_name="this_model.name"
|
||||
:right_counts="right_counts"
|
||||
:left_counts="left_counts"
|
||||
@reset="resetList"
|
||||
@get-list="getItems"
|
||||
@item-action="startAction">
|
||||
<template v-slot:cards-left>
|
||||
<generic-horizontal-card
|
||||
v-for="i in items_left" v-bind:key="i.id"
|
||||
:item=i
|
||||
:model="this_model"
|
||||
:draggable="true"
|
||||
@item-action="startAction($event, 'left')"/>
|
||||
</template>
|
||||
<template v-slot:cards-right>
|
||||
<generic-horizontal-card v-for="i in items_right" v-bind:key="i.id"
|
||||
:item=i
|
||||
:model="this_model"
|
||||
:draggable="true"
|
||||
@item-action="startAction($event, 'right')"/>
|
||||
</template>
|
||||
</generic-split-lists>
|
||||
:model="this_model"
|
||||
:action="this_action"
|
||||
:item1="this_item"
|
||||
:item2="this_target"
|
||||
:show="show_modal"
|
||||
@finish-action="finishAction"/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-2 d-none d-md-block">
|
||||
</div>
|
||||
<div class="col-xl-8 col-12">
|
||||
<div class="container-fluid d-flex flex-column flex-grow-1">
|
||||
<div class="row">
|
||||
<div class="col-md-6" style="margin-top: 1vh">
|
||||
<h3>
|
||||
<!-- <span><b-button variant="link" size="sm" class="text-dark shadow-none"><i class="fas fa-chevron-down"></i></b-button></span> -->
|
||||
<model-menu/>
|
||||
<span>{{this.this_model.name}}</span>
|
||||
<span><b-button variant="link" size="lg" @click="startAction({'action':'new'})"><i class="fas fa-plus-circle"></i></b-button></span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="col-md-3" />
|
||||
<div class="col-md-3" style="position: relative; margin-top: 1vh">
|
||||
<b-form-checkbox v-model="show_split" name="check-button" v-if="paginated"
|
||||
class="shadow-none"
|
||||
style="position:relative;top: 50%; transform: translateY(-50%);" switch>
|
||||
{{ $t('show_split_screen') }}
|
||||
</b-form-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" >
|
||||
<div class="col" :class="{'col-md-6' : show_split}">
|
||||
<!-- model isn't paginated and loads in one API call -->
|
||||
<div v-if="!paginated">
|
||||
<generic-horizontal-card v-for="i in items_left" v-bind:key="i.id"
|
||||
:item=i
|
||||
:model="this_model"
|
||||
@item-action="startAction($event, 'left')"/>
|
||||
</div>
|
||||
<!-- model is paginated and needs managed -->
|
||||
<generic-infinite-cards v-if="paginated"
|
||||
:card_counts="left_counts"
|
||||
:scroll="show_split"
|
||||
@search="getItems($event, 'left')"
|
||||
@reset="resetList('left')">
|
||||
<template v-slot:cards>
|
||||
<generic-horizontal-card
|
||||
v-for="i in items_left" v-bind:key="i.id"
|
||||
:item=i
|
||||
:model="this_model"
|
||||
@item-action="startAction($event, 'left')"/>
|
||||
</template>
|
||||
</generic-infinite-cards>
|
||||
</div>
|
||||
<div class="col col-md-6" v-if="show_split">
|
||||
<generic-infinite-cards v-if="this_model"
|
||||
:card_counts="right_counts"
|
||||
:scroll="show_split"
|
||||
@search="getItems($event, 'right')"
|
||||
@reset="resetList('right')">
|
||||
<template v-slot:cards>
|
||||
<generic-horizontal-card
|
||||
v-for="i in items_right" v-bind:key="i.id"
|
||||
:item=i
|
||||
:model="this_model"
|
||||
@item-action="startAction($event, 'right')"/>
|
||||
</template>
|
||||
</generic-infinite-cards>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
@ -46,9 +89,10 @@ import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
import {CardMixin, ApiMixin} from "@/utils/utils";
|
||||
import {StandardToasts, ToastMixin} from "@/utils/utils";
|
||||
|
||||
import GenericSplitLists from "@/components/GenericSplitLists";
|
||||
import GenericInfiniteCards from "@/components/GenericInfiniteCards";
|
||||
import GenericHorizontalCard from "@/components/GenericHorizontalCard";
|
||||
import GenericModalForm from "@/components/Modals/GenericModalForm";
|
||||
import ModelMenu from "@/components/ModelMenu";
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
@ -57,7 +101,7 @@ export default {
|
||||
// or i'm capturing it incorrectly
|
||||
name: 'ModelListView',
|
||||
mixins: [CardMixin, ApiMixin, ToastMixin],
|
||||
components: {GenericHorizontalCard, GenericSplitLists, GenericModalForm},
|
||||
components: {GenericHorizontalCard, GenericModalForm, GenericInfiniteCards, ModelMenu},
|
||||
data() {
|
||||
return {
|
||||
// this.Models and this.Actions inherited from ApiMixin
|
||||
@ -66,11 +110,14 @@ export default {
|
||||
right_counts: {'max': 9999, 'current': 0},
|
||||
left_counts: {'max': 9999, 'current': 0},
|
||||
this_model: undefined,
|
||||
model_menu: undefined,
|
||||
this_action: undefined,
|
||||
this_recipe_param: undefined,
|
||||
this_item: {},
|
||||
this_target: {},
|
||||
show_modal: false
|
||||
show_modal: false,
|
||||
show_split: false,
|
||||
paginated: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@ -78,6 +125,13 @@ export default {
|
||||
let model_config = JSON.parse(document.getElementById('model_config').textContent)
|
||||
this.this_model = this.Models[model_config?.model]
|
||||
this.this_recipe_param = model_config?.recipe_param
|
||||
this.paginated = this.this_model?.paginated ?? false
|
||||
this.$nextTick(() => {
|
||||
if (!this.paginated) {
|
||||
this.getItems()
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
methods: {
|
||||
// this.genericAPI inherited from ApiMixin
|
||||
@ -165,13 +219,14 @@ export default {
|
||||
}
|
||||
this.clearState()
|
||||
},
|
||||
getItems: function (params) {
|
||||
let column = params?.column ?? 'left'
|
||||
getItems: function (params, col) {
|
||||
let column = col || 'left'
|
||||
this.genericAPI(this.this_model, this.Actions.LIST, params).then((result) => {
|
||||
if (result.data.results.length) {
|
||||
this['items_' + column] = this['items_' + column].concat(result.data?.results)
|
||||
this[column + '_counts']['max'] = result.data.count
|
||||
this[column + '_counts']['current'] = this['items_' + column].length
|
||||
let results = result.data?.results ?? result.data
|
||||
if (results?.length) {
|
||||
this['items_' + column] = this['items_' + column].concat(results)
|
||||
this[column + '_counts']['max'] = result.data?.count ?? 0
|
||||
this[column + '_counts']['current'] = this['items_' + column]?.length
|
||||
} else {
|
||||
this[column + '_counts']['max'] = 0
|
||||
this[column + '_counts']['current'] = 0
|
||||
|
@ -155,7 +155,6 @@
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import {BootstrapVue} from 'bootstrap-vue'
|
||||
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
|
||||
import {apiLoadRecipe} from "@/utils/api";
|
||||
|
92
vue/src/components/GenericInfiniteCards.vue
Normal file
92
vue/src/components/GenericInfiniteCards.vue
Normal file
@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div id="app" style="margin-bottom: 4vh">
|
||||
<div class="row flex-shrink-0">
|
||||
<div class="col col-md">
|
||||
<!-- search box -->
|
||||
<b-input-group class="mt-3">
|
||||
<b-input class="form-control" type="search" v-model="search_left"
|
||||
v-bind:placeholder="this.text.search"></b-input>
|
||||
</b-input-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" :class="{'vh-100 mh-100 overflow-auto' : scroll}">
|
||||
<div class="col col-md">
|
||||
<slot name="cards"></slot>
|
||||
<infinite-loading
|
||||
:identifier='column'
|
||||
@infinite="infiniteHandler"
|
||||
spinner="waveDots">
|
||||
<template v-slot:no-more><span/></template>
|
||||
<template v-slot:no-results><span>{{$t('No_Results')}}</span></template>
|
||||
</infinite-loading>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
import _debounce from 'lodash/debounce'
|
||||
import InfiniteLoading from 'vue-infinite-loading';
|
||||
|
||||
export default {
|
||||
name: 'GenericInfiniteCards',
|
||||
components: {InfiniteLoading},
|
||||
props: {
|
||||
card_list: {type: Array, default(){return []}},
|
||||
card_counts: {type: Object},
|
||||
scroll: {type:Boolean, default: false}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
search: '',
|
||||
page: 0,
|
||||
state: undefined,
|
||||
column: +new Date(),
|
||||
text: {
|
||||
'new': '',
|
||||
'search': this.$t('Search')
|
||||
},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
watch: {
|
||||
search: _debounce(function() {
|
||||
this.page = 0
|
||||
this.$emit('reset')
|
||||
this.column += 1
|
||||
}, 700),
|
||||
|
||||
card_counts: {
|
||||
deep: true,
|
||||
handler(newVal, oldVal) {
|
||||
if (newVal.current > 0) {
|
||||
this.state.loaded()
|
||||
}
|
||||
if (newVal.current >= newVal.max) {
|
||||
this.state.complete()
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
infiniteHandler: function($state, col) {
|
||||
let params = {
|
||||
'query': this.search,
|
||||
'page': this.page + 1
|
||||
}
|
||||
this.state = $state
|
||||
this.$emit('search', params)
|
||||
this.page+= 1
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
44
vue/src/components/GenericPill.vue
Normal file
44
vue/src/components/GenericPill.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div v-if="itemList">
|
||||
<span :key="k.id" v-for="k in itemList" class="pl-1">
|
||||
<b-badge pill :variant="color">{{thisLabel(k)}}</b-badge>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'GenericPill',
|
||||
props: {
|
||||
item_list: {required: true, type: Array},
|
||||
label: {type: String, default: 'name'},
|
||||
color: {type: String, default: 'light'}
|
||||
},
|
||||
computed: {
|
||||
itemList: function() {
|
||||
if(Array.isArray(this.item_list)) {
|
||||
return this.item_list
|
||||
} else if (!this.item_list?.id) {
|
||||
return false
|
||||
} else {
|
||||
return [this.item_list]
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
thisLabel: function (item) {
|
||||
let fields = this.label.split('::')
|
||||
let value = item
|
||||
fields.forEach(x => {
|
||||
value = value[x]
|
||||
});
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -26,8 +26,11 @@
|
||||
</td>
|
||||
<td v-if="detailed">
|
||||
<div v-if="ingredient.note">
|
||||
<span v-b-popover.hover="ingredient.note"
|
||||
class="d-print-none"> <i class="far fa-comment"></i>
|
||||
<span v-b-popover.hover="ingredient.note" v-if="ingredient.note.length > 15"
|
||||
class="d-print-none touchable"> <i class="far fa-comment"></i>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ ingredient.note }}
|
||||
</span>
|
||||
|
||||
<div class="d-none d-print-block">
|
||||
@ -72,3 +75,13 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* increase size of hover/touchable space without changing spacing */
|
||||
.touchable {
|
||||
padding-right: 2em;
|
||||
padding-left: 2em;
|
||||
margin-right: -2em;
|
||||
margin-left: -2em;
|
||||
}
|
||||
</style>
|
||||
|
61
vue/src/components/ModelMenu.vue
Normal file
61
vue/src/components/ModelMenu.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<!-- <b-button variant="link" size="sm" class="text-dark shadow-none"><i class="fas fa-chevron-down"></i></b-button> -->
|
||||
<span>
|
||||
<b-dropdown variant="link" toggle-class="text-decoration-none text-dark shadow-none" no-caret style="boundary:window">
|
||||
<template #button-content>
|
||||
<i class="fas fa-chevron-down">
|
||||
</template>
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_food')">
|
||||
<i class="fas fa-leaf fa-fw"></i> {{ Models['FOOD'].name }}
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_keyword')">
|
||||
<i class="fas fa-tags fa-fw"></i> {{ Models['KEYWORD'].name }}
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_unit')">
|
||||
<i class="fas fa-balance-scale fa-fw"></i> {{ Models['UNIT'].name }}
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_supermarket')">
|
||||
<i class="fas fa-store-alt fa-fw"></i> {{ Models['SUPERMARKET'].name }}
|
||||
</b-dropdown-item>
|
||||
|
||||
<b-dropdown-item :href="resolveDjangoUrl('list_supermarket_category')">
|
||||
<i class="fas fa-cubes fa-fw"></i> {{ Models['SHOPPING_CATEGORY'].name }}
|
||||
</b-dropdown-item>
|
||||
|
||||
</b-dropdown>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Vue from 'vue'
|
||||
import {BootstrapVue} from 'bootstrap-vue'
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
|
||||
import {Models} from "@/utils/models";
|
||||
import {ResolveUrlMixin} from "@/utils/utils";
|
||||
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
export default {
|
||||
name: 'ModelMenu',
|
||||
mixins: [ResolveUrlMixin],
|
||||
data() {
|
||||
return {
|
||||
Models: Models
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
gotoURL: function(model) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
@ -12,7 +12,7 @@
|
||||
"all_fields_optional": "All fields are optional and can be left empty.",
|
||||
"convert_internal": "Convert to internal recipe",
|
||||
"show_only_internal": "Show only internal recipes",
|
||||
"show_split_screen": "Show split view",
|
||||
"show_split_screen": "Split View",
|
||||
|
||||
"Log_Recipe_Cooking": "Log Recipe Cooking",
|
||||
"External_Recipe_Image": "External Recipe Image",
|
||||
|
@ -62,9 +62,13 @@ export class Models {
|
||||
'name': i18n.t('Food'), // *OPTIONAL* : parameters will be built model -> model_type -> default
|
||||
'apiName': 'Food', // *REQUIRED* : the name that is used in api.ts for this model
|
||||
'model_type': this.TREE, // *OPTIONAL* : model specific params for api, if not present will attempt modeltype_create then default_create
|
||||
'paginated': true,
|
||||
'move': true,
|
||||
'merge': true,
|
||||
'badges': {
|
||||
'linked_recipe': true
|
||||
'linked_recipe': true,
|
||||
},
|
||||
'tags': [{'field': 'supermarket_category', 'label': 'name', 'color': 'info'}],
|
||||
// REQUIRED: unordered array of fields that can be set during create
|
||||
'create': {
|
||||
// if not defined partialUpdate will use the same parameters, prepending 'id'
|
||||
@ -113,6 +117,9 @@ export class Models {
|
||||
'name': i18n.t('Keyword'), // *OPTIONAL: parameters will be built model -> model_type -> default
|
||||
'apiName': 'Keyword',
|
||||
'model_type': this.TREE,
|
||||
'paginated': true,
|
||||
'move': true,
|
||||
'merge': true,
|
||||
'badges': {
|
||||
'icon': true
|
||||
},
|
||||
@ -146,6 +153,7 @@ export class Models {
|
||||
static UNIT = {
|
||||
'name': i18n.t('Unit'),
|
||||
'apiName': 'Unit',
|
||||
'paginated': true,
|
||||
'create': {
|
||||
'params': [['name', 'description']],
|
||||
'form': {
|
||||
@ -165,7 +173,7 @@ export class Models {
|
||||
}
|
||||
}
|
||||
},
|
||||
'move': false
|
||||
'merge': true
|
||||
}
|
||||
static SHOPPING_LIST = {}
|
||||
static RECIPE_BOOK = {
|
||||
@ -220,6 +228,53 @@ export class Models {
|
||||
}
|
||||
},
|
||||
}
|
||||
static SHOPPING_CATEGORY_RELATION = {
|
||||
'name': i18n.t('Shopping_Category'),
|
||||
'apiName': 'SupermarketCategory',
|
||||
'create': {
|
||||
'params': [['category', 'supermarket', 'order']],
|
||||
'form': {
|
||||
'name': {
|
||||
'form_field': true,
|
||||
'type': 'text',
|
||||
'field': 'name',
|
||||
'label': i18n.t('Name'),
|
||||
'placeholder': ''
|
||||
},
|
||||
'description': {
|
||||
'form_field': true,
|
||||
'type': 'text',
|
||||
'field': 'description',
|
||||
'label': i18n.t('Description'),
|
||||
'placeholder': ''
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
static SUPERMARKET = {
|
||||
'name': i18n.t('Supermarket'),
|
||||
'apiName': 'Supermarket',
|
||||
'tags': [{'field': 'category_to_supermarket', 'label': 'category::name', 'color': 'info'}],
|
||||
'create': {
|
||||
'params': [['name', 'description', 'category_to_supermarket']],
|
||||
'form': {
|
||||
'name': {
|
||||
'form_field': true,
|
||||
'type': 'text',
|
||||
'field': 'name',
|
||||
'label': i18n.t('Name'),
|
||||
'placeholder': ''
|
||||
},
|
||||
'description': {
|
||||
'form_field': true,
|
||||
'type': 'text',
|
||||
'field': 'description',
|
||||
'label': i18n.t('Description'),
|
||||
'placeholder': ''
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
static RECIPE = {
|
||||
'name': i18n.t('Recipe'),
|
||||
|
Loading…
Reference in New Issue
Block a user