diff --git a/vue3/src/components/display/RandomIcon.vue b/vue3/src/components/display/RandomIcon.vue
new file mode 100644
index 000000000..194cd3324
--- /dev/null
+++ b/vue3/src/components/display/RandomIcon.vue
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vue3/src/components/inputs/ModelSelect.vue b/vue3/src/components/inputs/ModelSelect.vue
index 0115da0c9..c5dc245c4 100644
--- a/vue3/src/components/inputs/ModelSelect.vue
+++ b/vue3/src/components/inputs/ModelSelect.vue
@@ -124,7 +124,6 @@ onBeforeMount(() => {
* @param query input to search for on the API
*/
function search(query: string) {
- console.log('search called')
loading.value = true
return modelClass.value.list({query: query, page: 1, pageSize: 25}).then((r: any) => {
if (modelClass.value.model.isPaginated) {
diff --git a/vue3/src/pages/SearchPage.vue b/vue3/src/pages/SearchPage.vue
index ce352d89f..fd5c5d8cd 100644
--- a/vue3/src/pages/SearchPage.vue
+++ b/vue3/src/pages/SearchPage.vue
@@ -3,11 +3,11 @@
@@ -23,47 +23,20 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
- { filters[item].enabled = true; nextTick(() => {addFilterSelect = ''})}" density="compact" :label="$t('AddFilter')" v-model="addFilterSelect">
-
-
-
-
-
-
-
-
-
-
-
-
+ { filters[item].enabled = true; nextTick(() => {addFilterSelect = null})}" density="compact"
+ :label="$t('AddFilter')" v-model="addFilterSelect">
@@ -79,7 +52,7 @@
:items="[{title: $t('Table'), value: 'table'}, {title: $t('Cards'), value: 'grid'},]" density="compact">
-
@@ -87,7 +60,7 @@
-
+ {{ $t('Reset') }}
{{ $t('Search') }}
@@ -104,8 +77,8 @@
:loading="loading"
:items="recipes"
:headers="tableHeaders"
- :page="search_page"
- :items-per-page="search_pageSize"
+ :page="page"
+ :items-per-page="pageSize"
:items-length="tableItemCount"
@click:row="handleRowClick"
disable-sort
@@ -113,7 +86,8 @@
hide-default-footer
>
-
+
+
@@ -139,8 +113,8 @@
-
@@ -179,39 +153,187 @@ import {useDisplay} from "vuetify";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
import {useRouteQuery} from "@vueuse/router";
import {toNumberArray} from "@/utils/utils";
+import RandomIcon from "@/components/display/RandomIcon.vue";
const {t} = useI18n()
const router = useRouter()
const {mdAndUp} = useDisplay()
-const search_query = useRouteQuery('query', "",)
-const search_page = useRouteQuery('page', 1, {transform: Number})
-const search_pageSize = useRouteQuery('pageSize', useUserPreferenceStore().deviceSettings.general_tableItemsPerPage, {transform: Number})
-
-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})
+const query = useRouteQuery('query', "",)
+const page = useRouteQuery('page', 1, {transform: Number})
+const pageSize = useRouteQuery('pageSize', useUserPreferenceStore().deviceSettings.general_tableItemsPerPage, {transform: Number})
/**
* 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: []},
+ keywords: {
+ value: 'keywords',
+ label: 'Keyword (any)',
+ hint: 'Any of the given keywords',
+ enabled: false,
+ default: [],
+ is: ModelSelect,
+ model: 'Keyword',
+ modelValue: useRouteQuery('keywords', [], {transform: toNumberArray}),
+ mode: 'tags',
+ object: false,
+ searchOnLoad: true
+ },
+ keywordsAnd: {
+ value: 'keywordsAnd',
+ label: 'Keyword (all)',
+ hint: 'All of the given keywords',
+ enabled: false,
+ default: [],
+ is: ModelSelect,
+ model: 'Keyword',
+ modelValue: useRouteQuery('keywordsAnd', [], {transform: toNumberArray}),
+ mode: 'tags',
+ object: false,
+ searchOnLoad: true
+ },
+ keywordsOrNot: {
+ value: 'keywordsOrNot',
+ label: 'Keyword exclude (any)',
+ hint: 'Exclude recipes with any of the given keywords',
+ enabled: false,
+ default: [],
+ is: ModelSelect,
+ model: 'Keyword',
+ modelValue: useRouteQuery('keywordsOrNot ', [], {transform: toNumberArray}),
+ mode: 'tags',
+ object: false,
+ searchOnLoad: true
+ },
+ keywordsAndNot: {
+ value: 'keywordsAndNot',
+ label: 'Keyword exclude (all)',
+ hint: 'Exclude recipes with all of the given keywords',
+ enabled: false,
+ default: [],
+ is: ModelSelect,
+ model: 'Keyword',
+ modelValue: useRouteQuery('keywordsAndNot ', [], {transform: toNumberArray}),
+ mode: 'tags',
+ object: false,
+ searchOnLoad: true
+ },
+ foods: {
+ value: 'foods',
+ label: 'Foods (any)',
+ hint: 'Any of the given foods',
+ enabled: false,
+ default: [],
+ is: ModelSelect,
+ model: 'Food',
+ modelValue: useRouteQuery('foods', [], {transform: toNumberArray}),
+ mode: 'tags',
+ object: false,
+ searchOnLoad: true
+ },
+ foodsAnd: {
+ value: 'foodsAnd',
+ label: 'Food (all)',
+ hint: 'All of the given foods',
+ enabled: false,
+ default: [],
+ is: ModelSelect,
+ model: 'Food',
+ modelValue: useRouteQuery('foodsAnd', [], {transform: toNumberArray}),
+ mode: 'tags',
+ object: false,
+ searchOnLoad: true
+ },
+ foodsOrNot: {
+ value: 'foodsOrNot',
+ label: 'Food exclude (any)',
+ hint: 'Exclude recipes with any of the given foods',
+ enabled: false,
+ default: [],
+ is: ModelSelect,
+ model: 'Food',
+ modelValue: useRouteQuery('foodsOrNot ', [], {transform: toNumberArray}),
+ mode: 'tags',
+ object: false,
+ searchOnLoad: true
+ },
+ foodsAndNot: {
+ value: 'foodsAndNot',
+ label: 'Food exclude (all)',
+ hint: 'Exclude recipes with all of the given foods',
+ enabled: false,
+ default: [],
+ is: ModelSelect,
+ model: 'Food',
+ modelValue: useRouteQuery('foodsAndNot ', [], {transform: toNumberArray}),
+ mode: 'tags',
+ object: false,
+ searchOnLoad: true
+ },
+ books: {
+ value: 'books',
+ label: 'Book (any)',
+ hint: 'Recipes that are in any of the given books',
+ enabled: false,
+ default: [],
+ is: ModelSelect,
+ model: 'RecipeBook',
+ modelValue: useRouteQuery('books', [], {transform: toNumberArray}),
+ mode: 'tags',
+ object: false,
+ searchOnLoad: true
+ },
+ booksAnd: {
+ value: 'booksAnd',
+ label: 'Book (all)',
+ hint: 'Recipes that are in all of the given books',
+ enabled: false,
+ default: [],
+ is: ModelSelect,
+ model: 'RecipeBook',
+ modelValue: useRouteQuery('booksAnd', [], {transform: toNumberArray}),
+ mode: 'tags',
+ object: false,
+ searchOnLoad: true
+ },
+ booksOrNot: {
+ value: 'booksOrNot',
+ label: 'Book exclude (any)',
+ hint: 'Exclude recipes with any of the given books',
+ enabled: false,
+ default: [],
+ is: ModelSelect,
+ model: 'RecipeBook',
+ modelValue: useRouteQuery('booksOrNot ', [], {transform: toNumberArray}),
+ mode: 'tags',
+ object: false,
+ searchOnLoad: true
+ },
+ booksAndNot: {
+ value: 'booksAndNot',
+ label: 'Book exclude (all)',
+ hint: 'Exclude recipes with all of the given books',
+ enabled: false,
+ default: [],
+ is: ModelSelect,
+ model: 'RecipeBook',
+ modelValue: useRouteQuery('booksAndNot ', [], {transform: toNumberArray}),
+ mode: 'tags',
+ object: false,
+ searchOnLoad: true
+ },
})
/**
* filters that are not yet enabled
*/
const availableFilters = computed(() => {
- let f = []
+ let f: Array<{value: string, title: string}> = []
Object.entries(filters.value).forEach((entry) => {
- let [key, value] = entry
- if (!value.enabled) {
- f.push({value: value.value, title: value.title})
+ let [key, filter] = entry
+ if (!isFilterVisible(filter)) {
+ f.push({value: filter.value, title: filter.label})
}
})
return f
@@ -220,7 +342,7 @@ const availableFilters = computed(() => {
const loading = ref(false)
const dialog = ref(false)
const panel = ref('')
-const addFilterSelect = ref('')
+const addFilterSelect = ref(null)
const tableHeaders = computed(() => {
let headers = [
@@ -246,7 +368,7 @@ const newFilterName = ref('')
* 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(() => query.value, () => {
searchRecipes({page: 1})
})
@@ -254,7 +376,7 @@ watch(() => search_query.value, () => {
* perform initial search on mounted
*/
onMounted(() => {
- searchRecipes({page: search_page.value})
+ searchRecipes({page: page.value})
})
/**
@@ -265,20 +387,16 @@ function searchRecipes(options: VDataTableUpdateOptions) {
let api = new ApiApi()
loading.value = true
- search_page.value = options.page
- if (options.itemsPerPage) {
- search_pageSize.value = options.itemsPerPage
- }
-
let searchParameters = {
- query: search_query.value,
- page: search_page.value,
- pageSize: search_pageSize.value,
- keywords: search_keywords.value,
- foods: search_keywords.value,
- books: search_keywords.value,
+ query: query.value,
+ page: page.value,
+ pageSize: pageSize.value,
} as ApiRecipeListRequest
+ Object.values(filters.value).forEach((filter) => {
+ searchParameters[filter.value] = filter.modelValue
+ })
+
api.apiRecipeList(searchParameters).then((r) => {
recipes.value = r.results
tableItemCount.value = r.count
@@ -290,13 +408,25 @@ function searchRecipes(options: VDataTableUpdateOptions) {
})
}
+/**
+ * reset all search parameters and perform emtpy searchj
+ */
function reset() {
- Object.keys(urlSearchParams).forEach(key => {
- delete urlSearchParams[key]
+ page.value = 1
+ query.value = ''
+ Object.values(filters.value).forEach((filter) => {
+ filter.enabled = false
+ filter.modelValue = filter.default
})
recipes.value = []
+ searchRecipes({page: 1})
}
+/**
+ * handle clicking a table row by opening the selected recipe
+ * @param event
+ * @param data
+ */
function handleRowClick(event: PointerEvent, data: any) {
router.push({name: 'RecipeViewPage', params: {id: recipes.value[data.index].id}})
}
@@ -322,6 +452,19 @@ function saveCustomFilter() {
}
}
+/**
+ * determines if the filter should be visible because its either enabled or not the default value
+ * @param filter
+ */
+function isFilterVisible(filter: any) {
+ if (!filter.enabled && filter.modelValue.length > 0) {
+ filter.enabled = true
+ }
+ return filter.enabled
+}
+
+// TODO temporary function to convert old saved search format, either make proper db table or convert to native new format
+
/**
* create new filter
*/
@@ -347,8 +490,6 @@ function loadCustomFilter() {
})
}
-// TODO temporary function to convert old saved search format, either make proper db table or convert to native new format
-
/**
* turn data in the format of a CustomFilter into the format needed for api request
* @param customFilterParams