search prototype with keyword filters

This commit is contained in:
vabene1111
2025-03-29 12:09:19 +01:00
parent aa1fa3a40e
commit ebd354bc8d
33 changed files with 116 additions and 15 deletions

View File

@@ -6,6 +6,7 @@
"Activity": "", "Activity": "",
"Add": "", "Add": "",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "", "AddFoodToShopping": "",
"AddMany": "", "AddMany": "",
"AddToShopping": "", "AddToShopping": "",

View File

@@ -6,6 +6,7 @@
"Activity": "", "Activity": "",
"Add": "Добави", "Add": "Добави",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "Добавете {food} към списъка си за пазаруване", "AddFoodToShopping": "Добавете {food} към списъка си за пазаруване",
"AddMany": "", "AddMany": "",
"AddToShopping": "Добавяне към списъка за пазаруване", "AddToShopping": "Добавяне към списъка за пазаруване",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "", "Add": "",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "", "AddFoodToShopping": "",
"AddMany": "", "AddMany": "",
"AddToShopping": "", "AddToShopping": "",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "Přidat", "Add": "Přidat",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "Přidat {food} na váš nákupní seznam", "AddFoodToShopping": "Přidat {food} na váš nákupní seznam",
"AddMany": "", "AddMany": "",
"AddToShopping": "Přidat do nákupního seznamu", "AddToShopping": "Přidat do nákupního seznamu",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "Tilføj", "Add": "Tilføj",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "Tilføj {food} til indkøbsliste", "AddFoodToShopping": "Tilføj {food} til indkøbsliste",
"AddMany": "", "AddMany": "",
"AddToShopping": "Tilføj til indkøbsliste", "AddToShopping": "Tilføj til indkøbsliste",

View File

@@ -8,6 +8,7 @@
"Activity": "Aktivität", "Activity": "Aktivität",
"Add": "Hinzufügen", "Add": "Hinzufügen",
"AddAll": "Alle Hinzufügen", "AddAll": "Alle Hinzufügen",
"AddFilter": "Filter Hinzufügen",
"AddFoodToShopping": "Fügen Sie {food} zur Einkaufsliste hinzu", "AddFoodToShopping": "Fügen Sie {food} zur Einkaufsliste hinzu",
"AddMany": "Mehrere Hinzufügen", "AddMany": "Mehrere Hinzufügen",
"AddToShopping": "Zur Einkaufsliste hinzufügen", "AddToShopping": "Zur Einkaufsliste hinzufügen",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "Προσθήκη", "Add": "Προσθήκη",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "Προσθήκη του φαγητού {food} στη λίστα αγορών σας", "AddFoodToShopping": "Προσθήκη του φαγητού {food} στη λίστα αγορών σας",
"AddMany": "", "AddMany": "",
"AddToShopping": "Προσθήκη στη λίστα αγορών", "AddToShopping": "Προσθήκη στη λίστα αγορών",

View File

@@ -8,6 +8,7 @@
"Activity": "Activity", "Activity": "Activity",
"Add": "Add", "Add": "Add",
"AddAll": "Add all", "AddAll": "Add all",
"AddFilter": "Add Filter",
"AddFoodToShopping": "Add {food} to your shopping list", "AddFoodToShopping": "Add {food} to your shopping list",
"AddMany": "Add Many", "AddMany": "Add Many",
"AddToShopping": "Add to shopping list", "AddToShopping": "Add to shopping list",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "Añadir", "Add": "Añadir",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "Añadir {food} a la lista de la compra", "AddFoodToShopping": "Añadir {food} a la lista de la compra",
"AddMany": "", "AddMany": "",
"AddToShopping": "Añadir a la lista de la compra", "AddToShopping": "Añadir a la lista de la compra",

View File

@@ -6,6 +6,7 @@
"Activity": "", "Activity": "",
"Add": "Lisää", "Add": "Lisää",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddMany": "", "AddMany": "",
"Add_Step": "Lisää Vaihe", "Add_Step": "Lisää Vaihe",
"Add_nutrition_recipe": "Lisää ravintoaine reseptiin", "Add_nutrition_recipe": "Lisää ravintoaine reseptiin",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "Ajouter", "Add": "Ajouter",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "Ajouter laliment {food} à votre liste de courses", "AddFoodToShopping": "Ajouter laliment {food} à votre liste de courses",
"AddMany": "", "AddMany": "",
"AddToShopping": "Ajouter à la liste de courses", "AddToShopping": "Ajouter à la liste de courses",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "הוספה", "Add": "הוספה",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "הוסף {מזון} לרשימת הקניות", "AddFoodToShopping": "הוסף {מזון} לרשימת הקניות",
"AddMany": "", "AddMany": "",
"AddToShopping": "הוסף לרשימת קניות", "AddToShopping": "הוסף לרשימת קניות",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "Hozzáadás", "Add": "Hozzáadás",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "{food} hozzáadása bevásárlólistához", "AddFoodToShopping": "{food} hozzáadása bevásárlólistához",
"AddMany": "", "AddMany": "",
"AddToShopping": "Hozzáadás a bevásárlólistához", "AddToShopping": "Hozzáadás a bevásárlólistához",

View File

@@ -6,6 +6,7 @@
"Activity": "", "Activity": "",
"Add": "", "Add": "",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddMany": "", "AddMany": "",
"Add_nutrition_recipe": "Ավելացնել սննդայնություն բաղադրատոմսին", "Add_nutrition_recipe": "Ավելացնել սննդայնություն բաղադրատոմսին",
"Add_to_Book": "", "Add_to_Book": "",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "Tambahkan", "Add": "Tambahkan",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "", "AddFoodToShopping": "",
"AddMany": "", "AddMany": "",
"AddToShopping": "", "AddToShopping": "",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "", "Add": "",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "", "AddFoodToShopping": "",
"AddMany": "", "AddMany": "",
"AddToShopping": "", "AddToShopping": "",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "Aggiungi", "Add": "Aggiungi",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "Aggiungi {food} alla tua lista della spesa", "AddFoodToShopping": "Aggiungi {food} alla tua lista della spesa",
"AddMany": "", "AddMany": "",
"AddToShopping": "Aggiungi a lista della spesa", "AddToShopping": "Aggiungi a lista della spesa",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "", "Add": "",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "", "AddFoodToShopping": "",
"AddMany": "", "AddMany": "",
"AddToShopping": "", "AddToShopping": "",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "Legg til", "Add": "Legg til",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "Legg til {food] i handlelisten din", "AddFoodToShopping": "Legg til {food] i handlelisten din",
"AddMany": "", "AddMany": "",
"AddToShopping": "Legg til i handleliste", "AddToShopping": "Legg til i handleliste",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "Voeg toe", "Add": "Voeg toe",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "Voeg {food} toe aan je boodschappenlijst", "AddFoodToShopping": "Voeg {food} toe aan je boodschappenlijst",
"AddMany": "", "AddMany": "",
"AddToShopping": "Voeg toe aan boodschappenlijst", "AddToShopping": "Voeg toe aan boodschappenlijst",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "Dodaj", "Add": "Dodaj",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "Dodaj {food} do swojej listy zakupów", "AddFoodToShopping": "Dodaj {food} do swojej listy zakupów",
"AddMany": "", "AddMany": "",
"AddToShopping": "Dodaj do listy zakupów", "AddToShopping": "Dodaj do listy zakupów",

View File

@@ -6,6 +6,7 @@
"Activity": "", "Activity": "",
"Add": "Adicionar", "Add": "Adicionar",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "Adicionar {food} à sua lista de compras", "AddFoodToShopping": "Adicionar {food} à sua lista de compras",
"AddMany": "", "AddMany": "",
"AddToShopping": "Adicionar á lista de compras", "AddToShopping": "Adicionar á lista de compras",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "Adicionar", "Add": "Adicionar",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "Incluir {food} na sua lista de compras", "AddFoodToShopping": "Incluir {food} na sua lista de compras",
"AddMany": "", "AddMany": "",
"AddToShopping": "Incluir na Lista de Compras", "AddToShopping": "Incluir na Lista de Compras",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "Adaugă", "Add": "Adaugă",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "Adăugă {food} în lista de cumpărături", "AddFoodToShopping": "Adăugă {food} în lista de cumpărături",
"AddMany": "", "AddMany": "",
"AddToShopping": "Adaugă la lista de cumpărături", "AddToShopping": "Adaugă la lista de cumpărături",

View File

@@ -6,6 +6,7 @@
"Activity": "", "Activity": "",
"Add": "Добавить", "Add": "Добавить",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "Добавить {food} в ваш список покупок", "AddFoodToShopping": "Добавить {food} в ваш список покупок",
"AddMany": "", "AddMany": "",
"AddToShopping": "Добавить в лист покупок", "AddToShopping": "Добавить в лист покупок",

View File

@@ -6,6 +6,7 @@
"Activity": "", "Activity": "",
"Add": "Dodaj", "Add": "Dodaj",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "Dodaj {food} v nakupovalni listek", "AddFoodToShopping": "Dodaj {food} v nakupovalni listek",
"AddMany": "", "AddMany": "",
"AddToShopping": "Dodaj nakupovlanemu listku", "AddToShopping": "Dodaj nakupovlanemu listku",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "Lägg till", "Add": "Lägg till",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "Lägg till {food} på din inköpslista", "AddFoodToShopping": "Lägg till {food} på din inköpslista",
"AddMany": "", "AddMany": "",
"AddToShopping": "Lägg till i inköpslista", "AddToShopping": "Lägg till i inköpslista",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "Ekle", "Add": "Ekle",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "{food}'ı alışveriş listenize ekleyin", "AddFoodToShopping": "{food}'ı alışveriş listenize ekleyin",
"AddMany": "", "AddMany": "",
"AddToShopping": "Alışveriş listesine ekle", "AddToShopping": "Alışveriş listesine ekle",

View File

@@ -6,6 +6,7 @@
"Activity": "", "Activity": "",
"Add": "Додати", "Add": "Додати",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "Додати {food} до вашого списку покупок", "AddFoodToShopping": "Додати {food} до вашого списку покупок",
"AddMany": "", "AddMany": "",
"AddToShopping": "Додати до списку покупок", "AddToShopping": "Додати до списку покупок",

View File

@@ -8,6 +8,7 @@
"Activity": "", "Activity": "",
"Add": "添加", "Add": "添加",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddFoodToShopping": "添加 {food} 到购物清单", "AddFoodToShopping": "添加 {food} 到购物清单",
"AddMany": "", "AddMany": "",
"AddToShopping": "添加到购物清单", "AddToShopping": "添加到购物清单",

View File

@@ -6,6 +6,7 @@
"Activity": "", "Activity": "",
"Add": "", "Add": "",
"AddAll": "", "AddAll": "",
"AddFilter": "",
"AddMany": "", "AddMany": "",
"Add_nutrition_recipe": "為食譜添加營養資訊", "Add_nutrition_recipe": "為食譜添加營養資訊",
"Add_to_Plan": "加入計劃", "Add_to_Plan": "加入計劃",

View File

@@ -7,6 +7,7 @@
:loading="loading" :loading="loading"
@submit="searchRecipes({page: 1})" @submit="searchRecipes({page: 1})"
@keydown.enter="searchRecipes({page: 1})" @keydown.enter="searchRecipes({page: 1})"
@click:clear="search_query = ''"
clearable hide-details> clearable hide-details>
<template v-slot:append> <template v-slot:append>
<v-btn @click="panel ='search' " v-if="panel == ''" color="primary" icon><i class="fa-solid fa-caret-down"></i></v-btn> <v-btn @click="panel ='search' " v-if="panel == ''" color="primary" icon><i class="fa-solid fa-caret-down"></i></v-btn>
@@ -22,7 +23,36 @@
<v-expansion-panel-text> <v-expansion-panel-text>
<v-form :disabled="loading" class="mt-4"> <v-form :disabled="loading" class="mt-4">
<model-select model="Keyword" mode="tags" v-model="search_keywords" density="compact" :object="false" search-on-load></model-select> <model-select model="Keyword" mode="tags" v-model="search_keywords" density="compact" :object="false" search-on-load
v-if="filters.keywords.enabled" :hint="filters.keywords.help">
<template #append>
<v-btn icon="fa-solid fa-times" size="small" variant="plain" @click="search_keywords = []; filters.keywords.enabled = false"></v-btn>
</template>
</model-select>
<model-select model="Keyword" mode="tags" v-model="search_keywords_and" density="compact" :object="false" search-on-load
v-if="filters.keywords_and.enabled">
<template #append>
<v-btn icon="fa-solid fa-times" size="small" variant="plain" @click="search_keywords_and = []; filters.keywords_and.enabled = false"></v-btn>
</template>
</model-select>
<model-select model="Keyword" mode="tags" v-model="search_keywords_or_not" density="compact" :object="false" search-on-load
v-if="filters.keywords_or_not.enabled">
<template #append>
<v-btn icon="fa-solid fa-times" size="small" variant="plain" @click="search_keywords_or_not = []; filters.keywords_or_not.enabled = false"></v-btn>
</template>
</model-select>
<model-select model="Keyword" mode="tags" v-model="search_keywords_and_not" density="compact" :object="false" search-on-load
v-if="filters.keywords_and_not.enabled">
<template #append>
<v-btn icon="fa-solid fa-times" size="small" variant="plain" @click="search_keywords_and_not = []; filters.keywords_and_not.enabled = false"></v-btn>
</template>
</model-select>
<v-divider class="mt-2 mb-2"></v-divider>
<v-autocomplete :items="availableFilters" @update:model-value="(item:string) =>{ filters[item].enabled = true; nextTick(() => {addFilterSelect = ''})}" density="compact" :label="$t('AddFilter')" v-model="addFilterSelect"></v-autocomplete>
<!-- <model-select model="Food" mode="tags" v-model="urlSearchParams.foods" density="compact" :object="false"></model-select>--> <!-- <model-select model="Food" mode="tags" v-model="urlSearchParams.foods" density="compact" :object="false"></model-select>-->
<!-- <model-select model="Unit" mode="tags" v-model="urlSearchParams.units" density="compact" :object="false"></model-select>--> <!-- <model-select model="Unit" mode="tags" v-model="urlSearchParams.units" density="compact" :object="false"></model-select>-->
<!-- <model-select model="RecipeBook" mode="tags" v-model="urlSearchParams.books" density="compact" :object="false"></model-select>--> <!-- <model-select model="RecipeBook" mode="tags" v-model="urlSearchParams.books" density="compact" :object="false"></model-select>-->
@@ -134,42 +164,63 @@
import {computed, nextTick, onMounted, ref, watch} from "vue"; import {computed, nextTick, onMounted, ref, watch} from "vue";
import {ApiApi, ApiRecipeListRequest, CustomFilter, RecipeOverview} from "@/openapi"; import {ApiApi, ApiRecipeListRequest, CustomFilter, RecipeOverview} from "@/openapi";
import {useUrlSearchParams} from "@vueuse/core";
import {useI18n} from "vue-i18n"; import {useI18n} from "vue-i18n";
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore"; import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore";
import ModelSelect from "@/components/inputs/ModelSelect.vue"; import ModelSelect from "@/components/inputs/ModelSelect.vue";
import {VNumberInput} from 'vuetify/labs/VNumberInput' import {VNumberInput} from 'vuetify/labs/VNumberInput'
import {VDateInput} from 'vuetify/labs/VDateInput' import {VDateInput} from 'vuetify/labs/VDateInput'
import RecipeContextMenu from "@/components/inputs/RecipeContextMenu.vue"; import RecipeContextMenu from "@/components/inputs/RecipeContextMenu.vue";
import {LocationQueryValue, useRoute, useRouter} from "vue-router"; import {useRouter} from "vue-router";
import KeywordsBar from "@/components/display/KeywordsBar.vue"; import KeywordsBar from "@/components/display/KeywordsBar.vue";
import {VDataTableUpdateOptions} from "@/vuetify"; import {VDataTableUpdateOptions} from "@/vuetify";
import VClosableCardTitle from "@/components/dialogs/VClosableCardTitle.vue"; import VClosableCardTitle from "@/components/dialogs/VClosableCardTitle.vue";
import RecipeCard from "@/components/display/RecipeCard.vue"; import RecipeCard from "@/components/display/RecipeCard.vue";
import {useDisplay} from "vuetify"; import {useDisplay} from "vuetify";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore"; import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
import * as url from "node:url";
import {useRouteQuery} from "@vueuse/router"; import {useRouteQuery} from "@vueuse/router";
import {toNumberArray} from "@/utils/utils";
const {t} = useI18n() const {t} = useI18n()
const router = useRouter() const router = useRouter()
const route = useRoute()
const {mdAndUp} = useDisplay() const {mdAndUp} = useDisplay()
//const urlSearchParams = useUrlSearchParams('history', {})
const toArray = (param: String | String[]) =>
Array.isArray(param) ? param : [param];
const search_query = useRouteQuery('query', "",) const search_query = useRouteQuery('query', "",)
const search_page = useRouteQuery('page', 1, {transform: Number}) const search_page = useRouteQuery('page', 1, {transform: Number})
const search_pageSize = useRouteQuery('pageSize', useUserPreferenceStore().deviceSettings.general_tableItemsPerPage, {transform: Number}) const search_pageSize = useRouteQuery('pageSize', useUserPreferenceStore().deviceSettings.general_tableItemsPerPage, {transform: Number})
const search_keywords = useRouteQuery('keywords', [], {transform: toArray})
const search_keywords = useRouteQuery('keywords', [], {transform: toNumberArray})
const search_keywords_or_not = useRouteQuery('keywords_or_not', [], {transform: toNumberArray})
const search_keywords_and = useRouteQuery('keywords_and', [], {transform: toNumberArray})
const search_keywords_and_not = useRouteQuery('keywords_and_not', [], {transform: toNumberArray})
/**
* all filters available to enable
*/
const filters = ref({
keywords: {value: 'keywords', title: 'Keywords', help: 'Any of the keywords', enabled: false, default: []},
keywords_and: {value: 'keywords_and', title: 'Keywords And', help: 'All of the keywords', enabled: false, default: []},
keywords_or_not: {value: 'keywords_or_not', title: 'Keywords Or Not', help: 'None of the given keywords', enabled: false, default: []},
keywords_and_not: {value: 'keywords_and_not', title: 'Keywords And Not', help: 'Not all of the given keywords', enabled: false, default: []},
})
/**
* filters that are not yet enabled
*/
const availableFilters = computed(() => {
let f = []
Object.entries(filters.value).forEach((entry) => {
let [key, value] = entry
if (!value.enabled) {
f.push({value: value.value, title: value.title})
}
})
return f
})
const loading = ref(false) const loading = ref(false)
const dialog = ref(false) const dialog = ref(false)
const panel = ref('') const panel = ref('')
const viewMode = ref('table') const addFilterSelect = ref('')
const tableHeaders = computed(() => { const tableHeaders = computed(() => {
let headers = [ let headers = [
@@ -191,16 +242,25 @@ const recipes = ref([] as RecipeOverview[])
const selectedCustomFilter = ref({} as CustomFilter) const selectedCustomFilter = ref({} as CustomFilter)
const newFilterName = ref('') const newFilterName = ref('')
// handle query updates when using the GlobalSearchDialog on the search page directly /**
* handle query updates when using the GlobalSearchDialog on the search page directly
*/
// TODO this also makes the search update on every stroke, do we want this?
watch(() => search_query.value, () => { watch(() => search_query.value, () => {
searchRecipes({page: 1}) searchRecipes({page: 1})
}) })
/**
* perform initial search on mounted
*/
onMounted(() => { onMounted(() => {
console.log(search_keywords.value)
searchRecipes({page: search_page.value}) searchRecipes({page: search_page.value})
}) })
/**
* perform the recipe search with the given options
* @param options
*/
function searchRecipes(options: VDataTableUpdateOptions) { function searchRecipes(options: VDataTableUpdateOptions) {
let api = new ApiApi() let api = new ApiApi()
loading.value = true loading.value = true
@@ -215,6 +275,8 @@ function searchRecipes(options: VDataTableUpdateOptions) {
page: search_page.value, page: search_page.value,
pageSize: search_pageSize.value, pageSize: search_pageSize.value,
keywords: search_keywords.value, keywords: search_keywords.value,
foods: search_keywords.value,
books: search_keywords.value,
} as ApiRecipeListRequest } as ApiRecipeListRequest
api.apiRecipeList(searchParameters).then((r) => { api.apiRecipeList(searchParameters).then((r) => {

View File

@@ -47,5 +47,13 @@ export function uploadRecipeImage(recipeId: number, file: File) {
}).finally(() => { }).finally(() => {
}) })
}
/**
* convert a string or an array of strings into an array of numbers
* useful for query parameter transformation
* @param param
*/
export function toNumberArray(param: string | string[]): number[]{
return Array.isArray(param) ? param.map(Number) : [parseInt(param)];
} }