fix RqueryFilterBuilder Hydration issues and move to script setup

This commit is contained in:
Kuchenpirat 2025-07-29 19:57:52 +00:00
commit c20448b219
2 changed files with 448 additions and 519 deletions

View file

@ -163,14 +163,14 @@
max-width="290px" max-width="290px"
min-width="auto" min-width="auto"
> >
<template #activator="{ props }"> <template #activator="{ props: activatorProps }">
<v-text-field <v-text-field
v-model="field.value" v-model="field.value"
persistent-hint persistent-hint
:prepend-icon="$globals.icons.calendar" :prepend-icon="$globals.icons.calendar"
variant="underlined" variant="underlined"
color="primary" color="primary"
v-bind="props" v-bind="activatorProps"
readonly readonly
/> />
</template> </template>
@ -184,53 +184,48 @@
</v-menu> </v-menu>
<RecipeOrganizerSelector <RecipeOrganizerSelector
v-else-if="field.type === Organizer.Category" v-else-if="field.type === Organizer.Category"
:model-value="field.organizers" v-model="field.organizers"
:selector-type="Organizer.Category" :selector-type="Organizer.Category"
:show-add="false" :show-add="false"
:show-label="false" :show-label="false"
:show-icon="false" :show-icon="false"
variant="underlined" variant="underlined"
@update:model-value="setOrganizerValues(field, index, $event)"
/> />
<RecipeOrganizerSelector <RecipeOrganizerSelector
v-else-if="field.type === Organizer.Tag" v-else-if="field.type === Organizer.Tag"
:model-value="field.organizers" v-model="field.organizers"
:selector-type="Organizer.Tag" :selector-type="Organizer.Tag"
:show-add="false" :show-add="false"
:show-label="false" :show-label="false"
:show-icon="false" :show-icon="false"
variant="underlined" variant="underlined"
@update:model-value="setOrganizerValues(field, index, $event)"
/> />
<RecipeOrganizerSelector <RecipeOrganizerSelector
v-else-if="field.type === Organizer.Tool" v-else-if="field.type === Organizer.Tool"
:model-value="field.organizers" v-model="field.organizers"
:selector-type="Organizer.Tool" :selector-type="Organizer.Tool"
:show-add="false" :show-add="false"
:show-label="false" :show-label="false"
:show-icon="false" :show-icon="false"
variant="underlined" variant="underlined"
@update:model-value="setOrganizerValues(field, index, $event)"
/> />
<RecipeOrganizerSelector <RecipeOrganizerSelector
v-else-if="field.type === Organizer.Food" v-else-if="field.type === Organizer.Food"
:model-value="field.organizers" v-model="field.organizers"
:selector-type="Organizer.Food" :selector-type="Organizer.Food"
:show-add="false" :show-add="false"
:show-label="false" :show-label="false"
:show-icon="false" :show-icon="false"
variant="underlined" variant="underlined"
@update:model-value="setOrganizerValues(field, index, $event)"
/> />
<RecipeOrganizerSelector <RecipeOrganizerSelector
v-else-if="field.type === Organizer.Household" v-else-if="field.type === Organizer.Household"
:model-value="field.organizers" v-model="field.organizers"
:selector-type="Organizer.Household" :selector-type="Organizer.Household"
:show-add="false" :show-add="false"
:show-label="false" :show-label="false"
:show-icon="false" :show-icon="false"
variant="underlined" variant="underlined"
@update:model-value="setOrganizerValues(field, index, $event)"
/> />
</v-col> </v-col>
<!-- right parenthesis --> <!-- right parenthesis -->
@ -297,7 +292,7 @@
</v-card> </v-card>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { VueDraggable } from "vue-draggable-plus"; import { VueDraggable } from "vue-draggable-plus";
import { useDebounceFn } from "@vueuse/core"; import { useDebounceFn } from "@vueuse/core";
import { useHouseholdSelf } from "~/composables/use-households"; import { useHouseholdSelf } from "~/composables/use-households";
@ -307,12 +302,7 @@ import type { LogicalOperator, QueryFilterJSON, QueryFilterJSONPart, RelationalK
import { useCategoryStore, useFoodStore, useHouseholdStore, useTagStore, useToolStore } from "~/composables/store"; import { useCategoryStore, useFoodStore, useHouseholdStore, useTagStore, useToolStore } from "~/composables/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";
export default defineNuxtComponent({ const props = defineProps({
components: {
VueDraggable,
RecipeOrganizerSelector,
},
props: {
fieldDefs: { fieldDefs: {
type: Array as () => FieldDefinition[], type: Array as () => FieldDefinition[],
required: true, required: true,
@ -321,9 +311,13 @@ export default defineNuxtComponent({
type: Object as () => QueryFilterJSON | null, type: Object as () => QueryFilterJSON | null,
default: null, default: null,
}, },
}, });
emits: ["input", "inputJSON"],
setup(props, context) { const emit = defineEmits<{
(event: "input", value: string | undefined): void;
(event: "inputJSON", value: QueryFilterJSON | undefined): void;
}>();
const { household } = useHouseholdSelf(); const { household } = useHouseholdSelf();
const { logOps, relOps, buildQueryFilterString, getFieldFromFieldDef, isOrganizerType } = useQueryFilterBuilder(); const { logOps, relOps, buildQueryFilterString, getFieldFromFieldDef, isOrganizerType } = useQueryFilterBuilder();
@ -337,6 +331,7 @@ export default defineNuxtComponent({
datePickers: [] as boolean[], datePickers: [] as boolean[],
drag: false, drag: false,
}); });
const { showAdvanced, datePickers, drag } = toRefs(state);
const storeMap = { const storeMap = {
[Organizer.Category]: useCategoryStore(), [Organizer.Category]: useCategoryStore(),
@ -369,7 +364,7 @@ export default defineNuxtComponent({
id: useUid(), id: useUid(),
}); });
state.datePickers.push(false); state.datePickers.push(false);
}; }
function setField(index: number, fieldLabel: string) { function setField(index: number, fieldLabel: string) {
state.datePickers[index] = false; state.datePickers[index] = false;
@ -427,7 +422,7 @@ export default defineNuxtComponent({
function removeField(index: number) { function removeField(index: number) {
fields.value.splice(index, 1); fields.value.splice(index, 1);
state.datePickers.splice(index, 1); state.datePickers.splice(index, 1);
}; }
const fieldsUpdater = useDebounceFn((/* newFields: typeof fields.value */) => { const fieldsUpdater = useDebounceFn((/* newFields: typeof fields.value */) => {
/* newFields.forEach((field, index) => { /* newFields.forEach((field, index) => {
@ -441,8 +436,8 @@ export default defineNuxtComponent({
} }
state.qfValid = !!qf; state.qfValid = !!qf;
context.emit("input", qf || undefined); emit("input", qf || undefined);
context.emit("inputJSON", qf ? buildQueryFilterJSON() : undefined); emit("inputJSON", qf ? buildQueryFilterJSON() : undefined);
}, 500); }, 500);
watch(fields, fieldsUpdater, { deep: true }); watch(fields, fieldsUpdater, { deep: true });
@ -452,7 +447,7 @@ export default defineNuxtComponent({
return; return;
} }
field.organizers = []; const newField = { ...field, organizers: [] as OrganizerBase[] };
const { store, actions } = storeMap[field.type]; const { store, actions } = storeMap[field.type];
if (!store.value.length) { if (!store.value.length) {
@ -467,8 +462,8 @@ export default defineNuxtComponent({
} }
return organizer; return organizer;
}); });
field.organizers = organizers.filter(organizer => organizer !== undefined) as OrganizerBase[]; newField.organizers = organizers.filter(organizer => organizer !== undefined) as OrganizerBase[];
setOrganizerValues(field, index, field.organizers); setOrganizerValues(newField, index, newField.organizers);
} }
function initFieldsError(error = "") { function initFieldsError(error = "") {
@ -485,7 +480,7 @@ export default defineNuxtComponent({
function initializeFields() { function initializeFields() {
if (!props.initialQueryFilter?.parts?.length) { if (!props.initialQueryFilter?.parts?.length) {
return initFieldsError(); return initFieldsError();
}; }
const initFields: FieldWithId[] = []; const initFields: FieldWithId[] = [];
let error = false; let error = false;
@ -561,7 +556,7 @@ export default defineNuxtComponent({
else { else {
initFieldsError(); initFieldsError();
} }
}; }
try { try {
initializeFields(); initializeFields();
@ -643,30 +638,6 @@ export default defineNuxtComponent({
}, },
}; };
}); });
return {
Organizer,
...toRefs(state),
logOps,
relOps,
config,
firstDayOfWeek,
onDragEnd,
// Fields
fields,
addField,
setField,
setLeftParenthesisValue,
setRightParenthesisValue,
setLogicalOperatorValue,
setRelationalOperatorValue,
setFieldValue,
setFieldValues,
setOrganizerValues,
removeField,
};
},
});
</script> </script>
<style scoped> <style scoped>

View file

@ -3,7 +3,7 @@
v-model="selected" v-model="selected"
v-bind="inputAttrs" v-bind="inputAttrs"
v-model:search="searchInput" v-model:search="searchInput"
:items="storeItem" :items="items"
:label="label" :label="label"
chips chips
closable-chips closable-chips
@ -46,67 +46,40 @@
</v-autocomplete> </v-autocomplete>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import type { IngredientFood, RecipeCategory, RecipeTag } from "~/lib/api/types/recipe"; import type { IngredientFood, RecipeCategory, RecipeTag } from "~/lib/api/types/recipe";
import type { RecipeTool } from "~/lib/api/types/admin"; 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";
export default defineNuxtComponent({ interface Props {
props: { selectorType: RecipeOrganizer;
modelValue: { inputAttrs?: Record<string, any>;
type: Array as () => ( returnObject?: boolean;
showAdd?: boolean;
showLabel?: boolean;
showIcon?: boolean;
variant?: "filled" | "underlined" | "outlined" | "plain" | "solo" | "solo-inverted" | "solo-filled";
}
const props = withDefaults(defineProps<Props>(), {
inputAttrs: () => ({}),
returnObject: true,
showAdd: true,
showLabel: true,
showIcon: true,
variant: "outlined",
});
const selected = defineModel<(
| HouseholdSummary | HouseholdSummary
| RecipeTag | RecipeTag
| RecipeCategory | RecipeCategory
| RecipeTool | RecipeTool
| IngredientFood | IngredientFood
| string | string
)[] | undefined, )[] | undefined>({ required: true });
required: true,
},
/**
* The type of organizer to use.
*/
selectorType: {
type: String as () => RecipeOrganizer,
required: true,
},
inputAttrs: {
type: Object as () => Record<string, any>,
default: () => ({}),
},
returnObject: {
type: Boolean,
default: true,
},
showAdd: {
type: Boolean,
default: true,
},
showLabel: {
type: Boolean,
default: true,
},
showIcon: {
type: Boolean,
default: true,
},
variant: {
type: String as () => "filled" | "underlined" | "outlined" | "plain" | "solo" | "solo-inverted" | "solo-filled",
default: "outlined",
},
},
emits: ["update:modelValue"],
setup(props, context) {
const selected = computed({
get: () => props.modelValue,
set: (val) => {
context.emit("update:modelValue", val);
},
});
onMounted(() => { onMounted(() => {
if (selected.value === undefined) { if (selected.value === undefined) {
@ -205,21 +178,6 @@ export default defineNuxtComponent({
function resetSearchInput() { function resetSearchInput() {
searchInput.value = ""; searchInput.value = "";
} }
return {
Organizer,
appendCreated,
dialog,
storeItem: items,
label,
icon,
selected,
removeByIndex,
searchInput,
resetSearchInput,
};
},
});
</script> </script>
<style scoped> <style scoped>