mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-12-11 12:55:15 -05:00
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:
@@ -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"),
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user