mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-01 04:10:06 -05:00
meal plan editor tweaks
This commit is contained in:
@@ -1,153 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-dialog activator="parent" v-model="dialog" max-width="1200">
|
|
||||||
<template v-slot:default="{ isActive }">
|
|
||||||
<v-card style="overflow: auto">
|
|
||||||
<v-card-title>Meal Plan Edit
|
|
||||||
<v-btn icon="fas fa-times" variant="flat" size="x-small" class="mt-2 float-right " @click="isActive.value = false"></v-btn>
|
|
||||||
</v-card-title>
|
|
||||||
<v-divider></v-divider>
|
|
||||||
<v-card-text>
|
|
||||||
<v-form>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" md="6">
|
|
||||||
<v-text-field label="Title" v-model="mutableMealPlan.title"></v-text-field>
|
|
||||||
<v-date-input
|
|
||||||
v-model="dateRangeValue"
|
|
||||||
label="Plan Date"
|
|
||||||
multiple="range"
|
|
||||||
prepend-icon=""
|
|
||||||
prepend-inner-icon="$calendar"
|
|
||||||
></v-date-input>
|
|
||||||
|
|
||||||
<v-input>
|
|
||||||
<v-btn-group elevation="1" class="w-100" divided border>
|
|
||||||
<v-btn class="w-25" @click="adjustDateRangeLength(dateRangeValue,-1)"><i class="fa-solid fa-minus"></i></v-btn>
|
|
||||||
<v-btn class="w-25" @click="dateRangeValue = shiftDateRange(dateRangeValue, -1)"><i class="fa-solid fa-angles-left"></i></v-btn>
|
|
||||||
<v-btn class="w-25" @click="dateRangeValue = shiftDateRange(dateRangeValue, +1)"><i class="fa-solid fa-angles-right"></i></v-btn>
|
|
||||||
<v-btn class="w-25" @click="adjustDateRangeLength(dateRangeValue,+1)"><i class="fa-solid fa-plus"></i></v-btn>
|
|
||||||
</v-btn-group>
|
|
||||||
</v-input>
|
|
||||||
|
|
||||||
<ModelSelect model="MealType" :allow-create="true" v-model="mutableMealPlan.mealType"></ModelSelect>
|
|
||||||
<v-number-input control-variant="split" :min="0" v-model="mutableMealPlan.servings" label="Servings"></v-number-input>
|
|
||||||
<ModelSelect model="User" :allow-create="false" v-model="mutableMealPlan.shared" item-label="displayName" mode="tags"></ModelSelect>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" md="6">
|
|
||||||
<ModelSelect model="Recipe" v-model="mutableMealPlan.recipe" @update:modelValue="mutableMealPlan.servings = mutableMealPlan.recipe?.servings ? mutableMealPlan.recipe?.servings : 1"></ModelSelect>
|
|
||||||
<!-- <v-number-input label="Days" control-variant="split" :min="1"></v-number-input>--> <!--TODO create days input with +/- synced to date -->
|
|
||||||
<recipe-card :recipe="mutableMealPlan.recipe" v-if="mutableMealPlan && mutableMealPlan.recipe"></recipe-card>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
<v-textarea label="Note" v-model="mutableMealPlan.note"></v-textarea>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-form>
|
|
||||||
</v-card-text>
|
|
||||||
<v-divider></v-divider>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-btn color="error" @click="useMealPlanStore().deleteObject(mutableMealPlan); dialog = false">
|
|
||||||
Delete
|
|
||||||
</v-btn>
|
|
||||||
<v-btn color="success" class="ml-auto" @click="saveMealPlan">
|
|
||||||
Save
|
|
||||||
</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
</v-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import {onMounted, PropType, ref, watch, watchEffect} from "vue";
|
|
||||||
import {ApiApi, MealPlan, RecipeOverview} from "@/openapi";
|
|
||||||
import {DateTime} from "luxon";
|
|
||||||
import RecipeCard from "@/components/display/RecipeCard.vue";
|
|
||||||
import {useMealPlanStore} from "@/stores/MealPlanStore";
|
|
||||||
import {VNumberInput} from 'vuetify/labs/VNumberInput' //TODO remove once component is out of labs
|
|
||||||
import {VDateInput} from 'vuetify/labs/VDateInput' //TODO remove once component is out of labs
|
|
||||||
import ModelSelect from "@/components/inputs/ModelSelect.vue";
|
|
||||||
import {useMessageStore} from "@/stores/MessageStore";
|
|
||||||
import {adjustDateRangeLength, shiftDateRange} from "@/utils/date_utils";
|
|
||||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
|
||||||
|
|
||||||
const props = defineProps(
|
|
||||||
{
|
|
||||||
mealPlan: {type: Object as PropType<MealPlan>, required: false},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const dialog = ref(false)
|
|
||||||
let mutableMealPlan = ref(newMealPlan())
|
|
||||||
const dateRangeValue = ref([] as Date[])
|
|
||||||
|
|
||||||
if (props.mealPlan != undefined) {
|
|
||||||
mutableMealPlan.value = props.mealPlan
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* once dialog is opened check if a meal plan prop is given, if so load it as the default values
|
|
||||||
*/
|
|
||||||
watch(dialog, () => {
|
|
||||||
if (dialog.value && props.mealPlan != undefined) {
|
|
||||||
mutableMealPlan.value = props.mealPlan
|
|
||||||
|
|
||||||
dateRangeValue.value = []
|
|
||||||
if (!dateRangeValue.value.includes(mutableMealPlan.value.fromDate)) {
|
|
||||||
dateRangeValue.value.push(mutableMealPlan.value.fromDate)
|
|
||||||
}
|
|
||||||
if (mutableMealPlan.value.toDate && !dateRangeValue.value.includes(mutableMealPlan.value.toDate)) {
|
|
||||||
dateRangeValue.value.push(mutableMealPlan.value.toDate)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
mutableMealPlan.value = newMealPlan()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* save meal plan into DB, parsing values from dateRange into meal plan object
|
|
||||||
*/
|
|
||||||
function saveMealPlan() {
|
|
||||||
|
|
||||||
if (mutableMealPlan.value != undefined) {
|
|
||||||
mutableMealPlan.value.recipe = mutableMealPlan.value.recipe as RecipeOverview
|
|
||||||
if (dateRangeValue.value != null) {
|
|
||||||
mutableMealPlan.value.fromDate = dateRangeValue.value[0]
|
|
||||||
mutableMealPlan.value.toDate = dateRangeValue.value[dateRangeValue.value.length - 1]
|
|
||||||
} else {
|
|
||||||
useMessageStore().addError('Missing Dates')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('calling save method')
|
|
||||||
useMealPlanStore().createOrUpdate(mutableMealPlan.value).catch(err => {
|
|
||||||
// TODO handle error
|
|
||||||
}).finally(() => {
|
|
||||||
dialog.value = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* create new meal plan on current date
|
|
||||||
*/
|
|
||||||
function newMealPlan() {
|
|
||||||
// TODO load default meal type
|
|
||||||
return {
|
|
||||||
fromDate: DateTime.now().toJSDate(),
|
|
||||||
toDate: DateTime.now().toJSDate(),
|
|
||||||
servings: 1,
|
|
||||||
shared: useUserPreferenceStore().userSettings.planShare
|
|
||||||
} as MealPlan
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style src="@vueform/multiselect/themes/default.css"></style>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<div class="align-self-center">
|
<div class="align-self-center">
|
||||||
<v-btn variant="flat" icon="">
|
<v-btn variant="flat" icon="">
|
||||||
<i class="fas fa-plus"></i>
|
<i class="fas fa-plus"></i>
|
||||||
<meal-plan-dialog></meal-plan-dialog>
|
<model-edit-dialog model="MealPlan"></model-edit-dialog>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
<v-list-item-subtitle>
|
<v-list-item-subtitle>
|
||||||
{{ p.mealType.name }}
|
{{ p.mealType.name }}
|
||||||
</v-list-item-subtitle>
|
</v-list-item-subtitle>
|
||||||
<meal-plan-dialog :meal-plan="p"></meal-plan-dialog>
|
<model-edit-dialog model="MealPlan" :item="p"></model-edit-dialog>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
|
||||||
</v-list>
|
</v-list>
|
||||||
@@ -55,14 +55,13 @@
|
|||||||
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {computed, onMounted, PropType, ref, toRefs} from 'vue'
|
import {computed, onMounted, ref} from 'vue'
|
||||||
import RecipeCard from "@/components/display/RecipeCard.vue";
|
|
||||||
import {useDisplay} from "vuetify";
|
import {useDisplay} from "vuetify";
|
||||||
import {MealPlan, Recipe, RecipeOverview} from "@/openapi";
|
import {MealPlan} from "@/openapi";
|
||||||
import {useMealPlanStore} from "@/stores/MealPlanStore";
|
import {useMealPlanStore} from "@/stores/MealPlanStore";
|
||||||
import {DateTime} from "luxon";
|
import {DateTime} from "luxon";
|
||||||
import MealPlanDialog from "@/components/dialogs/MealPlanDialog.vue";
|
|
||||||
import {homePageCols} from "@/utils/breakpoint_utils";
|
import {homePageCols} from "@/utils/breakpoint_utils";
|
||||||
|
import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue";
|
||||||
|
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|||||||
@@ -17,9 +17,8 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<meal-plan-dialog :meal-plan="mealPlan"></meal-plan-dialog>
|
<model-edit-dialog model="MealPlan" :item="mealPlan" @delete="(args: MealPlan) => emit('delete', args)"></model-edit-dialog>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -28,12 +27,16 @@
|
|||||||
import {computed, PropType} from "vue";
|
import {computed, PropType} from "vue";
|
||||||
import {IMealPlanNormalizedCalendarItem} from "@/types/MealPlan";
|
import {IMealPlanNormalizedCalendarItem} from "@/types/MealPlan";
|
||||||
import RecipeImage from "@/components/display/RecipeImage.vue";
|
import RecipeImage from "@/components/display/RecipeImage.vue";
|
||||||
import MealPlanDialog from "@/components/dialogs/MealPlanDialog.vue";
|
import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue";
|
||||||
|
import {MealPlan} from "@/openapi";
|
||||||
|
|
||||||
const emit = defineEmits({
|
const emit = defineEmits({
|
||||||
onDragStart: (value: IMealPlanNormalizedCalendarItem, event: DragEvent) => {
|
onDragStart: (value: IMealPlanNormalizedCalendarItem, event: DragEvent) => {
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
delete: (value: MealPlan) => {
|
||||||
|
return true
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let props = defineProps({
|
let props = defineProps({
|
||||||
|
|||||||
@@ -2,22 +2,25 @@
|
|||||||
<v-row class="h-100">
|
<v-row class="h-100">
|
||||||
<v-col>
|
<v-col>
|
||||||
<!-- TODO add hint about CTRL key while drag/drop -->
|
<!-- TODO add hint about CTRL key while drag/drop -->
|
||||||
<v-btn>
|
|
||||||
|
|
||||||
<model-edit-dialog model="MealPlan"></model-edit-dialog>
|
|
||||||
</v-btn>
|
|
||||||
|
|
||||||
<CalendarView
|
<CalendarView
|
||||||
:items="planItems"
|
:items="planItems"
|
||||||
class="theme-default"
|
class="theme-default"
|
||||||
:item-content-height="calendarItemHeight"
|
:item-content-height="calendarItemHeight"
|
||||||
:enable-drag-drop="true"
|
:enable-drag-drop="true"
|
||||||
@dropOnDate="dropCalendarItemOnDate">
|
@dropOnDate="dropCalendarItemOnDate"
|
||||||
|
@click-date="">
|
||||||
<template #header="{ headerProps }">
|
<template #header="{ headerProps }">
|
||||||
<CalendarViewHeader :header-props="headerProps"/>
|
<CalendarViewHeader :header-props="headerProps"/>
|
||||||
</template>
|
</template>
|
||||||
<template #item="{ value, weekStartDate, top }">
|
<template #item="{ value, weekStartDate, top }">
|
||||||
<meal-plan-calendar-item :item-height="calendarItemHeight" :value="value" :item-top="top" @onDragStart="currentlyDraggedMealplan = value" :detailed-items="lgAndUp"></meal-plan-calendar-item>
|
<meal-plan-calendar-item
|
||||||
|
:item-height="calendarItemHeight"
|
||||||
|
:value="value"
|
||||||
|
:item-top="top"
|
||||||
|
@onDragStart="currentlyDraggedMealplan = value"
|
||||||
|
@delete="(arg: MealPlan) => {useMealPlanStore().plans.delete(arg.id)}"
|
||||||
|
:detailed-items="lgAndUp"
|
||||||
|
></meal-plan-calendar-item>
|
||||||
</template>
|
</template>
|
||||||
</CalendarView>
|
</CalendarView>
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -37,8 +40,8 @@ import {computed, onMounted, ref} from "vue";
|
|||||||
import {DateTime} from "luxon";
|
import {DateTime} from "luxon";
|
||||||
import {useDisplay} from "vuetify";
|
import {useDisplay} from "vuetify";
|
||||||
import {useMealPlanStore} from "@/stores/MealPlanStore";
|
import {useMealPlanStore} from "@/stores/MealPlanStore";
|
||||||
import MealPlanEditor from "@/components/model_editors/MealPlanEditor.vue";
|
|
||||||
import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue";
|
import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue";
|
||||||
|
import {MealPlan} from "@/openapi";
|
||||||
|
|
||||||
const {lgAndUp} = useDisplay()
|
const {lgAndUp} = useDisplay()
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,10 @@
|
|||||||
|
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<v-text-field label="Title" v-model="editingObj.title"></v-text-field>
|
<v-text-field :label="$t('Title')" v-model="editingObj.title"></v-text-field>
|
||||||
<v-date-input
|
<v-date-input
|
||||||
v-model="dateRangeValue"
|
v-model="dateRangeValue"
|
||||||
label="Plan Date"
|
:label="$t('Date')"
|
||||||
multiple="range"
|
multiple="range"
|
||||||
prepend-icon=""
|
prepend-icon=""
|
||||||
prepend-inner-icon="$calendar"
|
prepend-inner-icon="$calendar"
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
</v-input>
|
</v-input>
|
||||||
|
|
||||||
<ModelSelect model="MealType" :allow-create="true" v-model="editingObj.mealType"></ModelSelect>
|
<ModelSelect model="MealType" :allow-create="true" v-model="editingObj.mealType"></ModelSelect>
|
||||||
<v-number-input control-variant="split" :min="0" v-model="editingObj.servings" label="Servings"></v-number-input>
|
<v-number-input control-variant="split" :min="0" v-model="editingObj.servings" :label="$t('Servings')"></v-number-input>
|
||||||
<ModelSelect model="User" :allow-create="false" v-model="editingObj.shared" item-label="displayName" mode="tags"></ModelSelect>
|
<ModelSelect model="User" :allow-create="false" v-model="editingObj.shared" item-label="displayName" mode="tags"></ModelSelect>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>
|
<v-col>
|
||||||
<v-textarea label="Note" v-model="editingObj.note"></v-textarea>
|
<v-textarea :label="$t('Note')" v-model="editingObj.note"></v-textarea>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
@@ -104,7 +104,14 @@ onMounted(() => {
|
|||||||
// initialize date range slider
|
// initialize date range slider
|
||||||
dateRangeValue.value.push(editingObj.value.fromDate)
|
dateRangeValue.value.push(editingObj.value.fromDate)
|
||||||
}, () => {
|
}, () => {
|
||||||
// TODO add all dates between start and end to date range
|
dateRangeValue.value.push(editingObj.value.fromDate)
|
||||||
|
if(editingObj.value.toDate && editingObj.value.toDate != editingObj.value.fromDate) {
|
||||||
|
let currentDate = DateTime.fromJSDate(editingObj.value.fromDate).plus({day: 1}).toJSDate()
|
||||||
|
while(currentDate <= editingObj.value.toDate){
|
||||||
|
dateRangeValue.value.push(currentDate)
|
||||||
|
currentDate = DateTime.fromJSDate(currentDate).plus({day: 1}).toJSDate()
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -119,7 +126,7 @@ function updateDate() {
|
|||||||
editingObj.value.fromDate = dateRangeValue.value[0]
|
editingObj.value.fromDate = dateRangeValue.value[0]
|
||||||
editingObj.value.toDate = dateRangeValue.value[dateRangeValue.value.length - 1]
|
editingObj.value.toDate = dateRangeValue.value[dateRangeValue.value.length - 1]
|
||||||
} else {
|
} else {
|
||||||
useMessageStore().addMessage(MessageType.WARNING, 'Food', 7000)
|
useMessageStore().addMessage(MessageType.WARNING, 'Missing Date', 7000)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export function useModelEditorFunctions<T>(modelName: EditorSupportedModels, emi
|
|||||||
let name = ''
|
let name = ''
|
||||||
if (editingObj.value.id) {
|
if (editingObj.value.id) {
|
||||||
modelClass.value.model.toStringKeys.forEach(key => {
|
modelClass.value.model.toStringKeys.forEach(key => {
|
||||||
name += ' ' + editingObj.value[key]
|
name += ' ' + key.split('.').reduce((a, b) => a[b], editingObj.value);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ export function useModelEditorFunctions<T>(modelName: EditorSupportedModels, emi
|
|||||||
*/
|
*/
|
||||||
function deleteObject() {
|
function deleteObject() {
|
||||||
modelClass.value.destroy(editingObj.value.id).then((r: any) => {
|
modelClass.value.destroy(editingObj.value.id).then((r: any) => {
|
||||||
emit('delete', editingObj)
|
emit('delete', editingObj.value)
|
||||||
editingObj.value = {} as T
|
editingObj.value = {} as T
|
||||||
}).catch((err: any) => {
|
}).catch((err: any) => {
|
||||||
useMessageStore().addError(ErrorMessageType.DELETE_ERROR, err)
|
useMessageStore().addError(ErrorMessageType.DELETE_ERROR, err)
|
||||||
|
|||||||
@@ -32,15 +32,12 @@ import RecipeCardComponent from "@/components/display/RecipeCard.vue"
|
|||||||
import GlobalSearchDialog from "@/components/inputs/GlobalSearchDialog.vue"
|
import GlobalSearchDialog from "@/components/inputs/GlobalSearchDialog.vue"
|
||||||
import RecipeCard from "@/components/display/RecipeCard.vue"
|
import RecipeCard from "@/components/display/RecipeCard.vue"
|
||||||
import HorizontalRecipeScroller from "@/components/display/HorizontalRecipeWindow.vue"
|
import HorizontalRecipeScroller from "@/components/display/HorizontalRecipeWindow.vue"
|
||||||
import {DateTime} from "luxon"
|
|
||||||
import {useMealPlanStore} from "@/stores/MealPlanStore"
|
|
||||||
import HorizontalMealPlanWindow from "@/components/display/HorizontalMealPlanWindow.vue"
|
import HorizontalMealPlanWindow from "@/components/display/HorizontalMealPlanWindow.vue"
|
||||||
import MealPlanDialog from "@/components/dialogs/MealPlanDialog.vue"
|
|
||||||
import ModelSelect from "@/components/inputs/ModelSelect.vue"
|
import ModelSelect from "@/components/inputs/ModelSelect.vue"
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "StartPage",
|
name: "StartPage",
|
||||||
components: {ModelSelect, MealPlanDialog, HorizontalMealPlanWindow, HorizontalRecipeScroller, RecipeCard, GlobalSearchDialog, RecipeCardComponent, KeywordsComponent},
|
components: {ModelSelect, HorizontalMealPlanWindow, HorizontalRecipeScroller, RecipeCard, GlobalSearchDialog, RecipeCardComponent, KeywordsComponent},
|
||||||
computed: {},
|
computed: {},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ export const TMealPlan = {
|
|||||||
icon: 'fa-solid fa-calendar-days',
|
icon: 'fa-solid fa-calendar-days',
|
||||||
|
|
||||||
isPaginated: true,
|
isPaginated: true,
|
||||||
toStringKeys: ['name'],
|
toStringKeys: ['title','recipe.name'],
|
||||||
|
|
||||||
tableHeaders: [
|
tableHeaders: [
|
||||||
{title: 'Title', key: 'title'},
|
{title: 'Title', key: 'title'},
|
||||||
@@ -404,6 +404,7 @@ export class GenericModel {
|
|||||||
api: Object
|
api: Object
|
||||||
model: Model
|
model: Model
|
||||||
// TODO find out the type of the t useI18n object and use it here
|
// TODO find out the type of the t useI18n object and use it here
|
||||||
|
// TODO decouple context from Generic model so t does not need to be passed
|
||||||
t: any
|
t: any
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user