doxfood/src/components/RandomConfigurationPanel.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>