feat: Add user QueryFilter and improve UI on mobile (#6235)

Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
Co-authored-by: Michael Genson <genson.michael@gmail.com>
This commit is contained in:
Arsène Reymond
2025-12-09 16:49:12 +01:00
committed by GitHub
parent 89aed15905
commit 6f7fba5ac1
7 changed files with 367 additions and 311 deletions

View File

@@ -83,6 +83,11 @@ const fieldDefs: FieldDefinition[] = [
label: i18n.t("household.households"), label: i18n.t("household.households"),
type: Organizer.Household, type: Organizer.Household,
}, },
{
name: "user_id",
label: i18n.t("user.users"),
type: Organizer.User,
},
{ {
name: "created_at", name: "created_at",
label: i18n.t("general.date-created"), label: i18n.t("general.date-created"),

View File

@@ -106,6 +106,11 @@ const fieldDefs: FieldDefinition[] = [
label: i18n.t("household.households"), label: i18n.t("household.households"),
type: Organizer.Household, type: Organizer.Household,
}, },
{
name: "user_id",
label: i18n.t("user.users"),
type: Organizer.User,
},
{ {
name: "last_made", name: "last_made",
label: i18n.t("general.last-made"), label: i18n.t("general.last-made"),

View File

@@ -1,283 +1,297 @@
<template> <template>
<v-card class="ma-0" style="overflow-x: auto;"> <v-card class="ma-0" flat fluid>
<v-card-text class="ma-0 pa-0"> <v-card-text class="ma-0 pa-0">
<v-container fluid class="ma-0 pa-0"> <VueDraggable
<VueDraggable v-model="fields"
v-model="fields" handle=".handle"
handle=".handle" :delay="250"
:delay="250" :delay-on-touch-only="true"
:delay-on-touch-only="true" v-bind="{
v-bind="{ animation: 200,
animation: 200, group: 'recipe-instructions',
group: 'recipe-instructions', ghostClass: 'ghost',
ghostClass: 'ghost', }"
}" @start="drag = true"
@start="drag = true" @end="onDragEnd"
@end="onDragEnd" >
<v-row
v-for="(field, index) in fields"
:key="field.id"
class="d-flex flex-row flex-wrap mx-auto pb-2"
:class="$vuetify.display.xs ? (Math.floor(index / 1) % 2 === 0 ? 'bg-dark' : 'bg-light') : ''"
style="max-width: 100%;"
> >
<v-row <!-- drag handle -->
v-for="(field, index) in fields" <v-col
:key="field.id" :cols="config.items.icon.cols(index)"
class="d-flex flex-nowrap" :sm="config.items.icon.sm(index)"
style="max-width: 100%;" :class="$vuetify.display.smAndDown ? 'd-flex pa-0' : 'd-flex justify-end pr-6'"
> >
<!-- drag handle --> <v-icon class="handle my-auto" :size="28" style="cursor: move;">
<v-col {{ $globals.icons.arrowUpDown }}
:cols="config.items.icon.cols" </v-icon>
:class="config.col.class" </v-col>
:style="config.items.icon.style"
<!-- and / or -->
<v-col
v-if="index != 0 || $vuetify.display.smAndUp"
:cols="config.items.logicalOperator.cols(index)"
:sm="config.items.logicalOperator.sm(index)"
:class="config.col.class"
>
<v-select
v-if="index"
:model-value="field.logicalOperator"
:items="[logOps.AND, logOps.OR]"
item-title="label"
item-value="value"
variant="underlined"
@update:model-value="setLogicalOperatorValue(field, index, $event as unknown as LogicalOperator)"
> >
<v-icon <template #chip="{ item }">
class="handle" <span :class="config.select.textClass" style="width: 100%;">
:size="24" {{ item.raw.label }}
style="cursor: move;margin: auto;" </span>
> </template>
{{ $globals.icons.arrowUpDown }} </v-select>
</v-icon> </v-col>
</v-col>
<!-- and / or --> <!-- left parenthesis -->
<v-col <v-col
:cols="config.items.logicalOperator.cols" v-if="showAdvanced"
:class="config.col.class" :cols="config.items.leftParens.cols(index)"
:style="config.items.logicalOperator.style" :sm="config.items.leftParens.sm(index)"
:class="config.col.class"
>
<v-select
:model-value="field.leftParenthesis"
:items="['', '(', '((', '(((']"
variant="underlined"
@update:model-value="setLeftParenthesisValue(field, index, $event)"
> >
<v-select <template #chip="{ item }">
v-if="index" <span :class="config.select.textClass" style="width: 100%;">
:model-value="field.logicalOperator" {{ item.raw }}
:items="[logOps.AND, logOps.OR]" </span>
item-title="label" </template>
item-value="value" </v-select>
variant="underlined" </v-col>
@update:model-value="setLogicalOperatorValue(field, index, $event as unknown as LogicalOperator)"
> <!-- field name -->
<template #chip="{ item }"> <v-col
<span :class="config.select.textClass" style="width: 100%;"> :cols="config.items.fieldName.cols(index)"
{{ item.raw.label }} :sm="config.items.fieldName.sm(index)"
</span> :class="config.col.class"
</template> >
</v-select> <v-select
</v-col> chips
<!-- left parenthesis --> :model-value="field.label"
<v-col :items="fieldDefs"
v-if="showAdvanced" variant="underlined"
:cols="config.items.leftParens.cols" item-title="label"
:class="config.col.class" @update:model-value="setField(index, $event)"
:style="config.items.leftParens.style"
> >
<v-select <template #chip="{ item }">
:model-value="field.leftParenthesis" <span :class="config.select.textClass" style="width: 100%;">
:items="['', '(', '((', '(((']" {{ item.raw.label }}
variant="underlined" </span>
@update:model-value="setLeftParenthesisValue(field, index, $event)" </template>
> </v-select>
<template #chip="{ item }"> </v-col>
<span :class="config.select.textClass" style="width: 100%;">
{{ item.raw }} <!-- relational operator -->
</span> <v-col
</template> :cols="config.items.relationalOperator.cols(index)"
</v-select> :sm="config.items.relationalOperator.sm(index)"
</v-col> :class="config.col.class"
<!-- field name --> >
<v-col <v-select
:cols="config.items.fieldName.cols" v-if="field.type !== 'boolean'"
:class="config.col.class" :model-value="field.relationalOperatorValue"
:style="config.items.fieldName.style" :items="field.relationalOperatorOptions"
item-title="label"
item-value="value"
variant="underlined"
@update:model-value="setRelationalOperatorValue(field, index, $event as unknown as RelationalKeyword | RelationalOperator)"
> >
<v-select <template #chip="{ item }">
chips <span :class="config.select.textClass" style="width: 100%;">
:model-value="field.label" {{ item.raw.label }}
:items="fieldDefs" </span>
variant="underlined" </template>
item-title="label" </v-select>
@update:model-value="setField(index, $event)" </v-col>
>
<template #chip="{ item }"> <!-- field value -->
<span :class="config.select.textClass" style="width: 100%;"> <v-col
{{ item.raw.label }} :cols="config.items.fieldValue.cols(index)"
</span> :sm="config.items.fieldValue.sm(index)"
</template> :class="config.col.class"
</v-select> >
</v-col> <v-select
<!-- relational operator --> v-if="field.fieldOptions"
<v-col :model-value="field.values"
:cols="config.items.relationalOperator.cols" :items="field.fieldOptions"
:class="config.col.class" item-title="label"
:style="config.items.relationalOperator.style" item-value="value"
multiple
variant="underlined"
@update:model-value="setFieldValues(field, index, $event)"
/>
<v-text-field
v-else-if="field.type === 'string'"
:model-value="field.value"
variant="underlined"
@update:model-value="setFieldValue(field, index, $event)"
/>
<v-text-field
v-else-if="field.type === 'number'"
:model-value="field.value"
type="number"
variant="underlined"
@update:model-value="setFieldValue(field, index, $event)"
/>
<v-checkbox
v-else-if="field.type === 'boolean'"
:model-value="field.value"
@update:model-value="setFieldValue(field, index, $event!)"
/>
<v-menu
v-else-if="field.type === 'date'"
v-model="datePickers[index]"
:close-on-content-click="false"
transition="scale-transition"
offset-y
max-width="290px"
min-width="auto"
> >
<v-select <template #activator="{ props: activatorProps }">
v-if="field.type !== 'boolean'" <v-text-field
:model-value="field.relationalOperatorValue" :model-value="field.value ? $d(new Date(field.value + 'T00:00:00')) : null"
:items="field.relationalOperatorOptions" persistent-hint
item-title="label" :prepend-icon="$globals.icons.calendar"
item-value="value" variant="underlined"
variant="underlined" color="primary"
@update:model-value="setRelationalOperatorValue(field, index, $event as unknown as RelationalKeyword | RelationalOperator)" v-bind="activatorProps"
> readonly
<template #chip="{ item }">
<span :class="config.select.textClass" style="width: 100%;">
{{ item.raw.label }}
</span>
</template>
</v-select>
</v-col>
<!-- field value -->
<v-col
:cols="config.items.fieldValue.cols"
:class="config.col.class"
:style="config.items.fieldValue.style"
>
<v-select
v-if="field.fieldOptions"
:model-value="field.values"
:items="field.fieldOptions"
item-title="label"
item-value="value"
multiple
variant="underlined"
@update:model-value="setFieldValues(field, index, $event)"
/>
<v-text-field
v-else-if="field.type === 'string'"
:model-value="field.value"
variant="underlined"
@update:model-value="setFieldValue(field, index, $event)"
/>
<v-text-field
v-else-if="field.type === 'number'"
:model-value="field.value"
type="number"
variant="underlined"
@update:model-value="setFieldValue(field, index, $event)"
/>
<v-checkbox
v-else-if="field.type === 'boolean'"
:model-value="field.value"
@update:model-value="setFieldValue(field, index, $event!)"
/>
<v-menu
v-else-if="field.type === 'date'"
v-model="datePickers[index]"
:close-on-content-click="false"
transition="scale-transition"
offset-y
max-width="290px"
min-width="auto"
>
<template #activator="{ props: activatorProps }">
<v-text-field
:model-value="field.value ? $d(new Date(field.value + 'T00:00:00')) : null"
persistent-hint
:prepend-icon="$globals.icons.calendar"
variant="underlined"
color="primary"
v-bind="activatorProps"
readonly
/>
</template>
<v-date-picker
:model-value="field.value ? new Date(field.value + 'T00:00:00') : null"
hide-header
:first-day-of-week="firstDayOfWeek"
:local="$i18n.locale"
@update:model-value="val => setFieldValue(field, index, val ? val.toISOString().slice(0, 10) : '')"
/> />
</v-menu> </template>
<RecipeOrganizerSelector <v-date-picker
v-else-if="field.type === Organizer.Category" :model-value="field.value ? new Date(field.value + 'T00:00:00') : null"
v-model="field.organizers" hide-header
:selector-type="Organizer.Category" :first-day-of-week="firstDayOfWeek"
:show-add="false" :local="$i18n.locale"
:show-label="false" @update:model-value="val => setFieldValue(field, index, val ? val.toISOString().slice(0, 10) : '')"
:show-icon="false"
variant="underlined"
@update:model-value="setFieldOrganizers(field, index, $event)"
/> />
<RecipeOrganizerSelector </v-menu>
v-else-if="field.type === Organizer.Tag" <RecipeOrganizerSelector
v-model="field.organizers" v-else-if="field.type === Organizer.Category"
:selector-type="Organizer.Tag" v-model="field.organizers"
:show-add="false" :selector-type="Organizer.Category"
:show-label="false" :show-add="false"
:show-icon="false" :show-label="false"
variant="underlined" :show-icon="false"
@update:model-value="setFieldOrganizers(field, index, $event)" variant="underlined"
/> @update:model-value="val => setFieldOrganizers(field, index, (val || []) as OrganizerBase[])"
<RecipeOrganizerSelector />
v-else-if="field.type === Organizer.Tool" <RecipeOrganizerSelector
v-model="field.organizers" v-else-if="field.type === Organizer.Tag"
:selector-type="Organizer.Tool" v-model="field.organizers"
:show-add="false" :selector-type="Organizer.Tag"
:show-label="false" :show-add="false"
:show-icon="false" :show-label="false"
variant="underlined" :show-icon="false"
@update:model-value="setFieldOrganizers(field, index, $event)" variant="underlined"
/> @update:model-value="val => setFieldOrganizers(field, index, (val || []) as OrganizerBase[])"
<RecipeOrganizerSelector />
v-else-if="field.type === Organizer.Food" <RecipeOrganizerSelector
v-model="field.organizers" v-else-if="field.type === Organizer.Tool"
:selector-type="Organizer.Food" v-model="field.organizers"
:show-add="false" :selector-type="Organizer.Tool"
:show-label="false" :show-add="false"
:show-icon="false" :show-label="false"
variant="underlined" :show-icon="false"
@update:model-value="setFieldOrganizers(field, index, $event)" variant="underlined"
/> @update:model-value="val => setFieldOrganizers(field, index, (val || []) as OrganizerBase[])"
<RecipeOrganizerSelector />
v-else-if="field.type === Organizer.Household" <RecipeOrganizerSelector
v-model="field.organizers" v-else-if="field.type === Organizer.Food"
:selector-type="Organizer.Household" v-model="field.organizers"
:show-add="false" :selector-type="Organizer.Food"
:show-label="false" :show-add="false"
:show-icon="false" :show-label="false"
variant="underlined" :show-icon="false"
@update:model-value="setFieldOrganizers(field, index, $event)" variant="underlined"
/> @update:model-value="val => setFieldOrganizers(field, index, (val || []) as OrganizerBase[])"
</v-col> />
<!-- right parenthesis --> <RecipeOrganizerSelector
<v-col v-else-if="field.type === Organizer.Household"
v-if="showAdvanced" v-model="field.organizers"
:cols="config.items.rightParens.cols" :selector-type="Organizer.Household"
:class="config.col.class" :show-add="false"
:style="config.items.rightParens.style" :show-label="false"
:show-icon="false"
variant="underlined"
@update:model-value="val => setFieldOrganizers(field, index, (val || []) as OrganizerBase[])"
/>
<RecipeOrganizerSelector
v-else-if="field.type === Organizer.User"
v-model="field.organizers"
:selector-type="Organizer.User"
:show-add="false"
:show-label="false"
:show-icon="false"
variant="underlined"
@update:model-value="val => setFieldOrganizers(field, index, (val || []) as OrganizerBase[])"
/>
</v-col>
<!-- right parenthesis -->
<v-col
v-if="showAdvanced"
:cols="config.items.rightParens.cols(index)"
:sm="config.items.rightParens.sm(index)"
:class="config.col.class"
>
<v-select
:model-value="field.rightParenthesis"
:items="['', ')', '))', ')))']"
variant="underlined"
@update:model-value="setRightParenthesisValue(field, index, $event)"
> >
<v-select <template #chip="{ item }">
:model-value="field.rightParenthesis" <span :class="config.select.textClass" style="width: 100%;">
:items="['', ')', '))', ')))']" {{ item.raw }}
variant="underlined" </span>
@update:model-value="setRightParenthesisValue(field, index, $event)" </template>
> </v-select>
<template #chip="{ item }"> </v-col>
<span :class="config.select.textClass" style="width: 100%;">
{{ item.raw }} <!-- field actions -->
</span> <v-col
</template> v-if="!$vuetify.display.smAndDown || index === fields.length - 1"
</v-select> :cols="config.items.fieldActions.cols(index)"
</v-col> :sm="config.items.fieldActions.sm(index)"
<!-- field actions --> :class="config.col.class"
<v-col >
:cols="config.items.fieldActions.cols" <BaseButtonGroup
:class="config.col.class" :buttons="[
:style="config.items.fieldActions.style" {
> icon: $globals.icons.delete,
<BaseButtonGroup text: $t('general.delete'),
:buttons="[ event: 'delete',
{ disabled: fields.length === 1,
icon: $globals.icons.delete, },
text: $t('general.delete'), ]"
event: 'delete', class="my-auto"
disabled: fields.length === 1, @delete="removeField(index)"
}, />
]" </v-col>
class="my-auto" </v-row>
@delete="removeField(index)" </VueDraggable>
/>
</v-col>
</v-row>
</VueDraggable>
</v-container>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-row fluid class="d-flex justify-end pa-0 mx-2"> <v-row fluid class="d-flex justify-end ma-2">
<v-spacer /> <v-spacer />
<v-checkbox <v-checkbox
v-model="showAdvanced" v-model="showAdvanced"
@@ -305,6 +319,7 @@ import RecipeOrganizerSelector from "~/components/Domain/Recipe/RecipeOrganizerS
import { Organizer } from "~/lib/api/types/non-generated"; import { Organizer } from "~/lib/api/types/non-generated";
import type { LogicalOperator, QueryFilterJSON, QueryFilterJSONPart, RelationalKeyword, RelationalOperator } from "~/lib/api/types/response"; import type { LogicalOperator, QueryFilterJSON, QueryFilterJSONPart, RelationalKeyword, RelationalOperator } from "~/lib/api/types/response";
import { useCategoryStore, useFoodStore, useHouseholdStore, useTagStore, useToolStore } from "~/composables/store"; import { useCategoryStore, useFoodStore, useHouseholdStore, useTagStore, useToolStore } from "~/composables/store";
import { useUserStore } from "~/composables/store/use-user-store";
import { type Field, type FieldDefinition, type FieldValue, type OrganizerBase, useQueryFilterBuilder } from "~/composables/use-query-filter-builder"; import { type Field, type FieldDefinition, type FieldValue, type OrganizerBase, useQueryFilterBuilder } from "~/composables/use-query-filter-builder";
const props = defineProps({ const props = defineProps({
@@ -344,6 +359,7 @@ const storeMap = {
[Organizer.Tool]: useToolStore(), [Organizer.Tool]: useToolStore(),
[Organizer.Food]: useFoodStore(), [Organizer.Food]: useFoodStore(),
[Organizer.Household]: useHouseholdStore(), [Organizer.Household]: useHouseholdStore(),
[Organizer.User]: useUserStore(),
}; };
function onDragEnd(event: any) { function onDragEnd(event: any) {
@@ -602,46 +618,56 @@ function buildQueryFilterJSON(): QueryFilterJSON {
} }
const config = computed(() => { const config = computed(() => {
const baseColMaxWidth = 55; const multiple = fields.value.length > 1;
const adv = state.showAdvanced;
return { return {
col: { col: {
class: "d-flex justify-center align-end field-col pa-1", class: "d-flex justify-center align-end py-0",
}, },
select: { select: {
textClass: "d-flex justify-center text-center", textClass: "d-flex justify-center text-center",
}, },
items: { items: {
icon: { icon: {
cols: 1, cols: (_index: number) => 2,
sm: (_index: number) => 1,
style: "width: fit-content;", style: "width: fit-content;",
}, },
leftParens: { leftParens: {
cols: state.showAdvanced ? 1 : 0, cols: (index: number) => (adv ? (index === 0 ? 2 : 0) : 0),
style: `min-width: ${state.showAdvanced ? baseColMaxWidth : 0}px;`, sm: (_index: number) => (adv ? 1 : 0),
}, },
logicalOperator: { logicalOperator: {
cols: 1, cols: (_index: number) => 0,
style: `min-width: ${baseColMaxWidth}px;`, sm: (_index: number) => (multiple ? 1 : 0),
}, },
fieldName: { fieldName: {
cols: state.showAdvanced ? 2 : 3, cols: (index: number) => {
style: `min-width: ${state.showAdvanced ? baseColMaxWidth * 2 : baseColMaxWidth * 3}px;`, if (adv) return index === 0 ? 8 : 12;
return index === 0 ? 10 : 12;
},
sm: (_index: number) => (adv ? 2 : 3),
}, },
relationalOperator: { relationalOperator: {
cols: 2, cols: (_index: number) => 12,
style: `min-width: ${baseColMaxWidth * 2}px;`, sm: (_index: number) => 2,
}, },
fieldValue: { fieldValue: {
cols: state.showAdvanced ? 3 : 4, cols: (index: number) => {
style: `min-width: ${state.showAdvanced ? baseColMaxWidth * 2 : baseColMaxWidth * 3}px;`, const last = index === fields.value.length - 1;
if (adv) return last ? 8 : 10;
return last ? 10 : 12;
},
sm: (_index: number) => (adv ? 3 : 4),
}, },
rightParens: { rightParens: {
cols: state.showAdvanced ? 1 : 0, cols: (index: number) => (adv ? (index === fields.value.length - 1 ? 2 : 0) : 0),
style: `min-width: ${state.showAdvanced ? baseColMaxWidth : 0}px;`, sm: (_index: number) => (adv ? 1 : 0),
}, },
fieldActions: { fieldActions: {
cols: 1, cols: (index: number) => (index === fields.value.length - 1 ? 2 : 0),
style: `min-width: ${baseColMaxWidth}px;`, sm: (_index: number) => 1,
}, },
}, },
}; };
@@ -651,5 +677,14 @@ const config = computed(() => {
<style scoped> <style scoped>
* { * {
font-size: 1em; font-size: 1em;
--bg-opactity: calc(var(--v-hover-opacity) * var(--v-theme-overlay-multiplier));
}
.bg-dark {
background-color: rgba(0, 0, 0, var(--bg-opactity));
}
.bg-light {
background-color: rgba(255, 255, 255, var(--bg-opactity));
} }
</style> </style>

View File

@@ -8,14 +8,15 @@
:label="label" :label="label"
chips chips
closable-chips closable-chips
item-title="name" :item-title="itemTitle"
item-value="name"
multiple multiple
:variant="variant" :variant="variant"
:prepend-inner-icon="icon" :prepend-inner-icon="icon"
:append-icon="showAdd ? $globals.icons.create : undefined" :append-icon="showAdd ? $globals.icons.create : undefined"
return-object return-object
auto-select-first auto-select-first
class="pa-0" class="pa-0 ma-0"
@update:model-value="resetSearchInput" @update:model-value="resetSearchInput"
@click:append="dialog = true" @click:append="dialog = true"
> >
@@ -33,7 +34,6 @@
{{ item.value }} {{ item.value }}
</v-chip> </v-chip>
</template> </template>
<template <template
v-if="showAdd" v-if="showAdd"
#append #append
@@ -53,12 +53,13 @@ import type { RecipeTool } from "~/lib/api/types/admin";
import { Organizer, type RecipeOrganizer } from "~/lib/api/types/non-generated"; import { Organizer, type RecipeOrganizer } from "~/lib/api/types/non-generated";
import type { HouseholdSummary } from "~/lib/api/types/household"; import type { HouseholdSummary } from "~/lib/api/types/household";
import { useCategoryStore, useFoodStore, useHouseholdStore, useTagStore, useToolStore } from "~/composables/store"; import { useCategoryStore, useFoodStore, useHouseholdStore, useTagStore, useToolStore } from "~/composables/store";
import { useUserStore } from "~/composables/store/use-user-store";
import { normalizeFilter } from "~/composables/use-utils"; import { normalizeFilter } from "~/composables/use-utils";
import type { UserSummary } from "~/lib/api/types/user";
interface Props { interface Props {
selectorType: RecipeOrganizer; selectorType: RecipeOrganizer;
inputAttrs?: Record<string, any>; inputAttrs?: Record<string, any>;
returnObject?: boolean;
showAdd?: boolean; showAdd?: boolean;
showLabel?: boolean; showLabel?: boolean;
showIcon?: boolean; showIcon?: boolean;
@@ -67,7 +68,6 @@ interface Props {
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
inputAttrs: () => ({}), inputAttrs: () => ({}),
returnObject: true,
showAdd: true, showAdd: true,
showLabel: true, showLabel: true,
showIcon: true, showIcon: true,
@@ -80,7 +80,7 @@ const selected = defineModel<(
| RecipeCategory | RecipeCategory
| RecipeTool | RecipeTool
| IngredientFood | IngredientFood
| string | UserSummary
)[] | undefined>({ required: true }); )[] | undefined>({ required: true });
onMounted(() => { onMounted(() => {
@@ -108,6 +108,8 @@ const label = computed(() => {
return i18n.t("general.foods"); return i18n.t("general.foods");
case Organizer.Household: case Organizer.Household:
return i18n.t("household.households"); return i18n.t("household.households");
case Organizer.User:
return i18n.t("user.users");
default: default:
return i18n.t("general.organizer"); return i18n.t("general.organizer");
} }
@@ -129,11 +131,19 @@ const icon = computed(() => {
return $globals.icons.foods; return $globals.icons.foods;
case Organizer.Household: case Organizer.Household:
return $globals.icons.household; return $globals.icons.household;
case Organizer.User:
return $globals.icons.user;
default: default:
return $globals.icons.tags; return $globals.icons.tags;
} }
}); });
const itemTitle = computed(() =>
props.selectorType === Organizer.User
? (i: any) => i?.fullName ?? i?.name ?? ""
: "name",
);
// =========================================================================== // ===========================================================================
// Store & Items Setup // Store & Items Setup
@@ -143,28 +153,19 @@ const storeMap = {
[Organizer.Tool]: useToolStore(), [Organizer.Tool]: useToolStore(),
[Organizer.Food]: useFoodStore(), [Organizer.Food]: useFoodStore(),
[Organizer.Household]: useHouseholdStore(), [Organizer.Household]: useHouseholdStore(),
[Organizer.User]: useUserStore(),
}; };
const store = computed(() => { const activeStore = computed(() => {
const { store } = storeMap[props.selectorType]; const { store } = storeMap[props.selectorType];
return store.value; return store.value;
}); });
const items = computed(() => { const items = computed<any[]>(() => {
if (!props.returnObject) { const list = (activeStore.value as unknown as any[]) ?? [];
return store.value.map(item => item.name); return list;
}
return store.value;
}); });
function removeByIndex(index: number) {
if (selected.value === undefined) {
return;
}
const newSelected = selected.value.filter((_, i) => i !== index);
selected.value = [...newSelected];
}
function appendCreated(item: any) { function appendCreated(item: any) {
if (selected.value === undefined) { if (selected.value === undefined) {
return; return;

View File

@@ -168,6 +168,7 @@ export function useQueryFilterBuilder() {
|| type === Organizer.Tool || type === Organizer.Tool
|| type === Organizer.Food || type === Organizer.Food
|| type === Organizer.Household || type === Organizer.Household
|| type === Organizer.User
); );
}; };

View File

@@ -29,7 +29,8 @@ export type RecipeOrganizer
| "tags" | "tags"
| "tools" | "tools"
| "foods" | "foods"
| "households"; | "households"
| "users";
export enum Organizer { export enum Organizer {
Category = "categories", Category = "categories",
@@ -37,4 +38,5 @@ export enum Organizer {
Tool = "tools", Tool = "tools",
Food = "foods", Food = "foods",
Household = "households", Household = "households",
User = "users",
} }

View File

@@ -76,13 +76,15 @@
can-confirm can-confirm
@confirm="saveQueryFilter" @confirm="saveQueryFilter"
> >
<QueryFilterBuilder <v-card-text>
:key="queryFilterMenuKey" <QueryFilterBuilder
:initial-query-filter="queryFilterJSON" :key="queryFilterMenuKey"
:field-defs="queryFilterBuilderFields" :initial-query-filter="queryFilterJSON"
@input="(value) => queryFilterEditorValue = value" :field-defs="queryFilterBuilderFields"
@input-j-s-o-n="(value) => queryFilterEditorValueJSON = value" @input="(value) => queryFilterEditorValue = value"
/> @input-j-s-o-n="(value) => queryFilterEditorValueJSON = value"
/>
</v-card-text>
<template #custom-card-action> <template #custom-card-action>
<BaseButton <BaseButton
color="error" color="error"
@@ -653,6 +655,11 @@ export default defineNuxtComponent({
label: i18n.t("household.households"), label: i18n.t("household.households"),
type: Organizer.Household, type: Organizer.Household,
}, },
{
name: "user_id",
label: i18n.t("user.users"),
type: Organizer.User,
},
]; ];
function clearQueryFilter() { function clearQueryFilter() {