235 lines
8.2 KiB
Vue
235 lines
8.2 KiB
Vue
<script setup lang="ts">
|
|
import { computed, onMounted, ref, watch } from "vue";
|
|
import TagsField from "./TagsField.vue";
|
|
import { pb } from "../pocketbase";
|
|
import Tag from "../models/tag";
|
|
import * as yup from "yup";
|
|
import MultiSelectField from "./MultiSelectField.vue";
|
|
import { storeToRefs } from "pinia";
|
|
import { useRestaurantsStore } from "../stores/restaurants";
|
|
import { useForm } from "vee-validate";
|
|
import { toTypedSchema } from "@vee-validate/yup";
|
|
import { makeFilters } from "../utils/filters";
|
|
import { SearchPreferences } from "../models/search";
|
|
import { usePreferencesStore } from "../stores/preferences";
|
|
|
|
const priceTicks = {
|
|
1: "€",
|
|
2: "€€",
|
|
3: "€€€",
|
|
4: "€€€€",
|
|
};
|
|
|
|
const { restaurants } = storeToRefs(useRestaurantsStore());
|
|
const { searchPreferences } = storeToRefs(usePreferencesStore());
|
|
|
|
const restaurantOptions = computed(() => {
|
|
return restaurants.value.map((restaurant) => restaurant.id);
|
|
});
|
|
|
|
const restaurantLabels = computed(() => {
|
|
const labels: Map<string, string> = new Map();
|
|
restaurants.value.forEach((restaurant) => {
|
|
labels.set(restaurant.id, restaurant.name);
|
|
});
|
|
return labels;
|
|
});
|
|
|
|
const availableTags = ref<Tag[]>([]);
|
|
|
|
const tagsOptions = computed(() => {
|
|
return availableTags.value.map((tag) => tag.id);
|
|
});
|
|
|
|
const tagsLabels = computed(() => {
|
|
const labels: Map<string, string> = new Map();
|
|
availableTags.value.forEach((tag) => {
|
|
labels.set(tag.id, tag.name);
|
|
});
|
|
return labels;
|
|
});
|
|
|
|
function mapValuesToModel(values: Object): SearchPreferences {
|
|
return {
|
|
priceRangeEnabled: values["priceRangeEnabled"],
|
|
includeTagsEnabled: values["includeTagsEnabled"],
|
|
excludeTagsEnabled: values["excludeTagsEnabled"],
|
|
includeRestaurantsEnabled: values["includeRestaurantsEnabled"],
|
|
excludeRestaurantsEnabled: values["excludeRestaurantsEnabled"],
|
|
lowerPriceBound: values["priceRange"] ? priceTicks[values["priceRange"][0]!] : undefined,
|
|
upperPriceBound: values["priceRange"] ? priceTicks[values["priceRange"][1]!] : undefined,
|
|
includedTags: values["includedTags"],
|
|
excludedTags: values["excludedTags"],
|
|
includedRestaurants: values["includedRestaurants"],
|
|
excludedRestaurants: values["excludedRestaurants"],
|
|
};
|
|
}
|
|
|
|
const { errors, values, handleSubmit, defineField } = useForm({
|
|
validationSchema: toTypedSchema(
|
|
yup.object({
|
|
priceRangeEnabled: yup.boolean(),
|
|
includeTagsEnabled: yup.boolean(),
|
|
excludeTagsEnabled: yup.boolean(),
|
|
includeRestaurantsEnabled: yup.boolean(),
|
|
excludeRestaurantsEnabled: yup.boolean(),
|
|
priceRange: yup.array().of(yup.number()).min(2).max(2),
|
|
includedTags: yup.array().of(yup.string()),
|
|
excludedTags: yup.array().of(yup.string()),
|
|
includedRestaurants: yup.array().of(yup.string()),
|
|
excludedRestaurants: yup.array().of(yup.string()),
|
|
})
|
|
),
|
|
initialValues: {
|
|
priceRangeEnabled: searchPreferences.value?.priceRangeEnabled,
|
|
includeTagsEnabled: searchPreferences.value?.includeTagsEnabled,
|
|
excludeTagsEnabled: searchPreferences.value?.excludeTagsEnabled,
|
|
includeRestaurantsEnabled: searchPreferences.value?.includeRestaurantsEnabled,
|
|
excludeRestaurantsEnabled: searchPreferences.value?.excludeRestaurantsEnabled,
|
|
priceRange: [
|
|
Number(Object.keys(priceTicks).find((key) => priceTicks[key] == searchPreferences.value?.lowerPriceBound ?? "€")),
|
|
Number(
|
|
Object.keys(priceTicks).find((key) => priceTicks[key] == searchPreferences.value?.upperPriceBound ?? "€€€€")
|
|
),
|
|
],
|
|
includedTags: searchPreferences.value?.includedTags,
|
|
excludedTags: searchPreferences.value?.excludedTags,
|
|
includedRestaurants: searchPreferences.value?.includedRestaurants,
|
|
excludedRestaurants: searchPreferences.value?.excludedRestaurants,
|
|
},
|
|
});
|
|
|
|
const filteredRestaurants = ref<number>(restaurants.value.length);
|
|
|
|
watch(
|
|
values,
|
|
async (newval, oldval) => {
|
|
const filters = makeFilters(mapValuesToModel(newval));
|
|
filteredRestaurants.value = useRestaurantsStore().getFilteredRestaurants(filters).length;
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
const [priceRangeEnabled, priceRangeEnabledAttrs] = defineField("priceRangeEnabled");
|
|
const [includeTagsEnabled, includeTagsEnabledAttrs] = defineField("includeTagsEnabled");
|
|
const [excludeTagsEnabled, excludeTagsEnabledAttrs] = defineField("excludeTagsEnabled");
|
|
const [includeRestaurantsEnabled, includeRestaurantsEnabledAttrs] = defineField("includeRestaurantsEnabled");
|
|
const [excludeRestaurantsEnabled, excludeRestaurantsEnabledAttrs] = defineField("excludeRestaurantsEnabled");
|
|
const [priceRange, priceRangeAttrs] = defineField("priceRange");
|
|
const [includedTags, includedTagsAttrs] = defineField("includedTags");
|
|
const [excludedTags, excludedTagsAttrs] = defineField("excludedTags");
|
|
const [includedRestaurants, includedRestaurantsAttrs] = defineField("includedRestaurants");
|
|
const [excludedRestaurants, excludedRestaurantsAttrs] = defineField("excludedRestaurants");
|
|
|
|
onMounted(async () => {
|
|
availableTags.value = await pb.collection("tags").getFullList();
|
|
|
|
pb.collection("tags").subscribe("*", async (e) => {
|
|
availableTags.value = await pb.collection("tags").getFullList();
|
|
});
|
|
});
|
|
|
|
const emit = defineEmits({
|
|
preferencesSaved: () => true,
|
|
});
|
|
|
|
const onSubmit = handleSubmit(async (values) => {
|
|
try {
|
|
const data = mapValuesToModel(values);
|
|
console.log(values);
|
|
await pb.collection("search_preferences").update(searchPreferences.value!.id!, data);
|
|
} catch (err) {
|
|
console.log(err);
|
|
}
|
|
|
|
emit("preferencesSaved");
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="flex flex-col place-content-center items-center h-full">
|
|
<div class="flex flex-row flex-nowrap w-full">
|
|
<div class="mx-5 my-4 w-full">
|
|
<label class="form-label mb-2">
|
|
<input type="checkbox" v-model="priceRangeEnabled" class="checkbox border-solid" />
|
|
Price range
|
|
</label>
|
|
<v-range-slider
|
|
v-model="priceRange"
|
|
show-ticks="always"
|
|
:min="1"
|
|
:max="4"
|
|
:step="1"
|
|
:ticks="priceTicks"
|
|
track-size="8"
|
|
:disabled="!priceRangeEnabled"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-row flex-nowrap w-full">
|
|
<div class="mx-5 my-4 w-full">
|
|
<label class="form-label mb-2">
|
|
<input type="checkbox" v-model="includeTagsEnabled" class="checkbox border-solid" />
|
|
Include tags
|
|
</label>
|
|
<TagsField
|
|
v-model="includedTags"
|
|
:create="false"
|
|
:options="tagsOptions"
|
|
:labels="tagsLabels"
|
|
:disabled="!includeTagsEnabled"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-row flex-nowrap w-full">
|
|
<div class="mx-5 my-4 w-full">
|
|
<label class="form-label mb-2">
|
|
<input type="checkbox" v-model="excludeTagsEnabled" class="checkbox border-solid" />
|
|
Exclude tags
|
|
</label>
|
|
<TagsField
|
|
v-model="excludedTags"
|
|
:create="false"
|
|
:options="tagsOptions"
|
|
:labels="tagsLabels"
|
|
:disabled="!excludeTagsEnabled"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-row flex-nowrap w-full">
|
|
<div class="mx-5 my-4 w-full">
|
|
<label class="form-label mb-2">
|
|
<input type="checkbox" v-model="includeRestaurantsEnabled" class="checkbox border-solid" />
|
|
Include restaurants
|
|
</label>
|
|
<MultiSelectField
|
|
v-model="includedRestaurants"
|
|
:options="restaurantOptions"
|
|
:labels="restaurantLabels"
|
|
:disabled="!includeRestaurantsEnabled"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-row flex-nowrap w-full">
|
|
<div class="mx-5 my-4 w-full">
|
|
<label class="form-label mb-2">
|
|
<input type="checkbox" v-model="excludeRestaurantsEnabled" class="checkbox border-solid" />
|
|
Exclude restaurants
|
|
</label>
|
|
<MultiSelectField
|
|
v-model="excludedRestaurants"
|
|
:options="restaurantOptions"
|
|
:labels="restaurantLabels"
|
|
:disabled="!excludeRestaurantsEnabled"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex-1"></div>
|
|
<div>
|
|
{{ errors }}
|
|
{{ filteredRestaurants }} filtered restaurants
|
|
<button @click="onSubmit" class="btn btn-outline w-full">Save preferences</button>
|
|
</div>
|
|
</template>
|