Merge branch 'pr/891' into develop

This commit is contained in:
vabene1111
2021-09-13 22:11:41 +02:00
22 changed files with 478 additions and 321 deletions

View File

@ -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

View File

@ -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";

View File

@ -1,34 +1,39 @@
<template>
<div row style="margin: 4px">
<b-card no-body d-flex flex-column :class="{'border border-primary' : over, 'shake': isError}"
style="height: 10vh;" :style="{'cursor:grab' : draggable}"
:style="{'cursor:grab' : useDrag}"
@dragover.prevent
@dragenter.prevent
:draggable="draggable"
:draggable="useDrag"
@dragstart="handleDragStart($event)"
@dragenter="handleDragEnter($event)"
@dragleave="handleDragLeave($event)"
@drop="handleDragDrop($event)">
<b-row no-gutters style="height:inherit;">
<b-col no-gutters md="3" style="height:inherit;">
<b-card-img-lazy style="object-fit: cover; height: 10vh;" :src="item_image" v-bind:alt="$t('Recipe_Image')"></b-card-img-lazy>
<b-row no-gutters >
<b-col no-gutters class="col-sm-3">
<b-card-img-lazy style="object-fit: cover; height: 6em;" :src="item_image" v-bind:alt="$t('Recipe_Image')"></b-card-img-lazy>
</b-col>
<b-col no-gutters md="9" style="height:inherit;">
<b-card-body class="m-0 py-0" style="height:inherit;">
<b-col no-gutters class="col-sm-9">
<b-card-body class="m-0 py-0">
<b-card-text class=" h-100 my-0 d-flex flex-column" style="text-overflow: ellipsis">
<h5 class="m-0 mt-1 text-truncate">{{ item[title] }}</h5>
<div class= "m-0 text-truncate">{{ item[subtitle] }}</div>
<div class="mt-auto mb-1 d-flex flex-row justify-content-end">
<div v-if="item[child_count]" class="mx-2 btn btn-link btn-sm"
<!-- <span>{{this_item[itemTags.field]}}</span> -->
<generic-pill v-for="x in itemTags" :key="x.field"
:item_list="item[x.field]"
:label="x.label"
:color="x.color"/>
<div class="mt-auto mb-1" align="right">
<span v-if="item[child_count]" class="mx-2 btn btn-link btn-sm"
style="z-index: 800;" v-on:click="$emit('item-action',{'action':'get-children','source':item})">
<div v-if="!item.show_children">{{ item[child_count] }} {{ itemName }}</div>
<div v-else>{{ text.hide_children }}</div>
</div>
<div v-if="item[recipe_count]" class="mx-2 btn btn-link btn-sm" style="z-index: 800;"
</span>
<span v-if="item[recipe_count]" class="mx-2 btn btn-link btn-sm" style="z-index: 800;"
v-on:click="$emit('item-action',{'action':'get-recipes','source':item})">
<div v-if="!item.show_recipes">{{ item[recipe_count] }} {{$t('Recipes')}}</div>
<div v-else>{{$t('Hide_Recipes')}}</div>
</div>
</span>
</div>
</b-card-text>
</b-card-body>
@ -45,24 +50,17 @@
</b-card>
<!-- recursively add child cards -->
<div class="row" v-if="item.show_children">
<div class="col-md-11 offset-md-1">
<div class="col-md-10 offset-md-2">
<generic-horizontal-card v-for="child in item[children]" v-bind:key="child.id"
:draggable="draggable"
:item="child"
:model="model"
:title="title"
:subtitle="subtitle"
:child_count="child_count"
:children="children"
:recipe_count="recipe_count"
:recipes="recipes"
@item-action="$emit('item-action', $event)">
</generic-horizontal-card>
</div>
</div>
<!-- conditionally view recipes -->
<div class="row" v-if="item.show_recipes">
<div class="col-md-11 offset-md-1">
<div class="col-md-10 offset-md-2">
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));grid-gap: 1rem;">
<recipe-card v-for="r in item[recipes]"
v-bind:key="r.id"
@ -91,19 +89,19 @@
<script>
import GenericContextMenu from "@/components/GenericContextMenu";
import Badges from "@/components/Badges";
import GenericPill from "@/components/GenericPill";
import RecipeCard from "@/components/RecipeCard";
import { mixin as clickaway } from 'vue-clickaway';
import { createPopper } from '@popperjs/core';
export default {
name: "GenericHorizontalCard",
components: { GenericContextMenu, RecipeCard, Badges},
components: { GenericContextMenu, RecipeCard, Badges, GenericPill},
mixins: [clickaway],
props: {
item: {type: Object},
model: {type: Object},
draggable: {type: Boolean, default: false},
title: {type: String, default: 'name'}, // this and the following props can probably be moved to model.js and made computed values
title: {type: String, default: 'name'}, // this and the following props need to be moved to model.js and made computed values
subtitle: {type: String, default: 'description'},
child_count: {type: String, default: 'numchild'},
children: {type: String, default: 'children'},
@ -134,10 +132,16 @@ export default {
return this.model?.name ?? "You Forgot To Set Model Name in model.js"
},
useMove: function() {
return this.model['move'] !== false
return (this.model?.['move'] ?? false) ? true : false
},
useMerge: function() {
return this.model['merge'] !== false
return (this.model?.['merge'] ?? false) ? true : false
},
useDrag: function() {
return this.useMove || this.useMerge
},
itemTags: function() {
return this.model?.tags ?? []
}
},
methods: {

View 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>

View 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>

View File

@ -1,220 +0,0 @@
<template>
<div id="app" style="margin-bottom: 4vh">
<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" :class="{'vh-100' : show_split}">
<!-- expanded options box -->
<div class="row flex-shrink-0">
<div class="col col-md-12">
<b-collapse id="collapse_advanced" class="mt-2" v-model="advanced_visible">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-md-3" style="margin-top: 1vh">
<div class="btn btn-primary btn-block text-uppercase" @click="$emit('item-action', {'action':'new'})">
{{ this.text.new }}
</div>
</div>
<div class="col-md-3" style="margin-top: 1vh">
<button class="btn btn-primary btn-block text-uppercase" @click="resetSearch">
{{ this.text.reset }}
</button>
</div>
<div class="col-md-3" style="position: relative; margin-top: 1vh">
<b-form-checkbox v-model="show_split" name="check-button"
class="shadow-none"
style="position:relative;top: 50%; transform: translateY(-50%);" switch>
{{ this.text.split }}
</b-form-checkbox>
</div>
</div>
</div>
</div>
</b-collapse>
</div>
</div>
<div class="row flex-shrink-0">
<!-- search box -->
<div class="col col-md">
<b-input-group class="mt-3">
<b-input class="form-control" v-model="search_left"
v-bind:placeholder="this.text.search"></b-input>
<b-input-group-append>
<b-button v-b-toggle.collapse_advanced variant="primary" class="shadow-none">
<i class="fas fa-caret-down" v-if="!advanced_visible"></i>
<i class="fas fa-caret-up" v-if="advanced_visible"></i>
</b-button>
</b-input-group-append>
</b-input-group>
</div>
<!-- split side search -->
<div class="col col-md" v-if="show_split">
<b-input-group class="mt-3">
<b-input class="form-control" v-model="search_right"
v-bind:placeholder="this.text.search"></b-input>
</b-input-group>
</div>
</div>
<!-- only show scollbars in split mode -->
<!-- TODO: weird behavior when switching to split mode, infinite scoll doesn't trigger if
bottom of page is in viewport can trigger by scrolling page (not column) up -->
<div class="row" :class="{'overflow-hidden' : show_split}">
<div class="col col-md" :class="{'mh-100 overflow-auto' : show_split}">
<slot name="cards-left"></slot>
<infinite-loading
:identifier='left'
@infinite="infiniteHandler($event, 'left')"
spinner="waveDots">
<template v-slot:no-more><span/></template>
<template v-slot:no-results><span>{{$t('No_Results')}}</span></template>
</infinite-loading>
</div>
<!-- right side cards -->
<div class="col col-md mh-100 overflow-auto" v-if="show_split">
<slot name="cards-right"></slot>
<infinite-loading
:identifier='right'
@infinite="infiniteHandler($event, 'right')"
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>
</div>
<div class="col-md-2 d-none d-md-block">
</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 {
// TODO: this should be simplified into a Generic Infinitely Scrolling List and added as two components when split lists desired
name: 'GenericSplitLists',
components: {InfiniteLoading},
props: {
list_name: {type: String, default: 'Blank List'}, // TODO update translations to handle plural translations
left_list: {type: Array, default(){return []}},
left_counts: {type: Object},
right_list: {type:Array, default(){return []}},
right_counts: {type: Object},
},
data() {
return {
advanced_visible: false,
show_split: false,
search_right: '',
search_left: '',
right_page: 0,
left_page: 0,
right_state: undefined,
left_state: undefined,
right_dirty: false,
left_dirty: false,
right: +new Date(),
left: +new Date(),
text: {
'new': '',
'name': '',
'reset': this.$t('Reset_Search'),
'split': this.$t('show_split_screen'),
'search': this.$t('Search')
},
}
},
mounted() {
this.dragMenu = this.$refs.tooltip
this.text.new = this.$t('New_' + this.list_name)
},
watch: {
search_left: _debounce(function() {
if (this.left_dirty) {
//prevents running twice if search is reset
this.left_dirty = false
return
}
this.left_page = 0
this.$emit('reset', {'column':'left'})
this.left += 1
}, 700),
search_right: _debounce(function(newVal, oldVal) {
if (this.right_dirty) {
//prevents running twice if search is reset
this.right_dirty = false
return
}
this.right_page = 0
this.$emit('reset', {'column':'right'})
this.right += 1
}, 700),
right_counts: {
deep: true,
handler(newVal, oldVal) {
if (newVal.current > 0) {
this.right_state.loaded()
}
if (newVal.current >= newVal.max) {
this.right_state.complete()
}
}
},
left_counts: {
deep: true,
handler(newVal, oldVal) {
if (newVal.current > 0) {
this.left_state.loaded()
}
if (newVal.current >= newVal.max) {
this.left_state.complete()
}
}
}
},
methods: {
resetSearch: function () {
this.right_dirty = true
this.search_right = ''
this.right_page = 0
this.right += 1
this.$emit('reset', {'column':'right'})
this.left_dirty = true
this.search_left = ''
this.left_page = 0
this.left += 1
this.$emit('reset', {'column':'left'})
},
infiniteHandler: function($state, col) {
let params = {
'query': (col==='left') ? this.search_left : this.search_right,
'page': (col==='left') ? this.left_page + 1 : this.right_page + 1,
'column': col
}
this[col+'_state'] = $state
this.$emit('get-list', params)
this[col+'_page'] += 1
},
}
}
</script>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
<style>
</style>

View File

@ -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>

View 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>

View File

@ -14,7 +14,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",
@ -46,7 +46,7 @@
"Edit_Recipe": "Edit Recipe",
"Move_Keyword": "Move Keyword",
"Merge_Keyword": "Merge Keyword",
"Hide_Keywords": "Hide Keywords",
"Hide_Keywords": "Hide Keyword",
"Hide_Recipes": "Hide Recipes",
"Move_Up": "Move up",
"Move_Down": "Move down",

View File

@ -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'),