Working on configuration panel for random restaurant
parent
fc7eb805b8
commit
1ebbe3579e
|
|
@ -25,6 +25,7 @@
|
|||
"vee-validate": "^4.13.0",
|
||||
"vue": "^3.4.21",
|
||||
"vue-router": "^4.3.2",
|
||||
"vuetify": "^3.6.10",
|
||||
"yup": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -2668,6 +2669,39 @@
|
|||
"vue": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vuetify": {
|
||||
"version": "3.6.10",
|
||||
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.6.10.tgz",
|
||||
"integrity": "sha512-Myd9+EFq4Gmu61yKPNVS0QdGQkcZ9cHom27wuvRw7jgDxM+X4MT9BwQRk/Dt1q3G3JlK8oh+ZYyq5Ps/Z73cMg==",
|
||||
"engines": {
|
||||
"node": "^12.20 || >=14.13"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/johnleider"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.7",
|
||||
"vite-plugin-vuetify": ">=1.0.0",
|
||||
"vue": "^3.3.0",
|
||||
"vue-i18n": "^9.0.0",
|
||||
"webpack-plugin-vuetify": ">=2.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
},
|
||||
"vite-plugin-vuetify": {
|
||||
"optional": true
|
||||
},
|
||||
"vue-i18n": {
|
||||
"optional": true
|
||||
},
|
||||
"webpack-plugin-vuetify": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
"vee-validate": "^4.13.0",
|
||||
"vue": "^3.4.21",
|
||||
"vue-router": "^4.3.2",
|
||||
"vuetify": "^3.6.10",
|
||||
"yup": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
.form-field {
|
||||
@apply input;
|
||||
@apply border-solid;
|
||||
@apply input-bordered;
|
||||
@apply w-full;
|
||||
}
|
||||
|
|
@ -17,6 +18,7 @@
|
|||
.form-select {
|
||||
@apply select;
|
||||
@apply select-bordered;
|
||||
@apply border-solid;
|
||||
@apply my-0;
|
||||
@apply w-full;
|
||||
}
|
||||
|
|
@ -24,6 +26,7 @@
|
|||
.form-field-error {
|
||||
@apply input;
|
||||
@apply input-bordered;
|
||||
@apply border-solid;
|
||||
@apply w-full;
|
||||
@apply input-error;
|
||||
}
|
||||
|
|
@ -31,6 +34,7 @@
|
|||
.form-select-error {
|
||||
@apply select;
|
||||
@apply select-bordered;
|
||||
@apply border-solid;
|
||||
@apply my-0;
|
||||
@apply w-full;
|
||||
@apply input-error;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { type PropType } from "vue";
|
||||
|
||||
import CreateTagModal from "./CreateTagModal.vue";
|
||||
|
||||
const props = defineProps({
|
||||
options: { type: Array as PropType<string[]> },
|
||||
labels: { type: Map as PropType<Map<string, string>>, default: () => {} },
|
||||
clearable: { type: Boolean, default: true },
|
||||
disabled: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const model = defineModel<string[]>();
|
||||
|
||||
const query = ref<string>("");
|
||||
|
||||
const createTagModal = ref<typeof CreateTagModal>();
|
||||
|
||||
function onBadgeClicked(value: string) {
|
||||
if (model.value?.includes(value)) {
|
||||
model.value = model.value.filter((val) => val != value);
|
||||
}
|
||||
}
|
||||
|
||||
function onOptionClicked(value: string) {
|
||||
if (model.value === undefined) {
|
||||
model.value = [value];
|
||||
} else if (model.value?.includes(value)) {
|
||||
model.value = model.value?.filter((val) => val !== value);
|
||||
} else {
|
||||
model.value = [...model.value, value];
|
||||
}
|
||||
|
||||
query.value = "";
|
||||
}
|
||||
|
||||
function showClear() {
|
||||
if (!props.clearable) {
|
||||
return false;
|
||||
}
|
||||
if (Array.isArray(model.value) && model.value?.length == 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function onClear() {
|
||||
model.value = [];
|
||||
}
|
||||
|
||||
function filteredOptions() {
|
||||
if (!query.value) return props.options;
|
||||
const lcquery = query.value.toLocaleLowerCase();
|
||||
return props.options?.filter((opt) => opt.toLocaleLowerCase().startsWith(lcquery));
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="dropdown" class="w-full combobox dropdown">
|
||||
<div
|
||||
:class="
|
||||
'input join input-bordered flex h-fit min-h-[3rem] w-full flex-row items-stretch px-0 focus-within:outline focus-within:outline-2 focus-within:outline-offset-2 ' +
|
||||
(props.disabled ? 'input-disabled' : '')
|
||||
"
|
||||
>
|
||||
<div class="mx-2 flex flex-wrap items-center">
|
||||
<div v-for="(value, idx) in model" :key="`${value}-${idx}`" class="badge m-1">
|
||||
<button class="mr-2" @click="onBadgeClicked(value)">
|
||||
<font-awesome-icon icon="xmark" />
|
||||
</button>
|
||||
{{ labels?.get(value) ?? value }}
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
ref="input"
|
||||
v-model="query"
|
||||
class="input join-item h-auto min-w-[8ch] flex-1 border-0 p-0 focus:outline-0"
|
||||
role="combobox"
|
||||
type="text"
|
||||
:disabled="props.disabled"
|
||||
/>
|
||||
<button
|
||||
v-show="showClear"
|
||||
:class="'btn join-item btn-ghost h-auto self-stretch ' + (props.disabled ? 'btn-disabled' : '')"
|
||||
@click="onClear"
|
||||
>
|
||||
<font-awesome-icon icon="xmark" />
|
||||
</button>
|
||||
</div>
|
||||
<ul
|
||||
v-if="!props.disabled"
|
||||
class="menu flex-nowrap dropdown-content overflow-y-auto z-10 max-h-96 w-full rounded-lg border border-neutral-content bg-base-100 p-0 shadow"
|
||||
>
|
||||
<slot></slot>
|
||||
<li v-for="option in filteredOptions()" :key="option" role="option" class="m-0 p-0">
|
||||
<button :class="{ active: model?.includes(option) }" @click="onOptionClicked(option)">
|
||||
<font-awesome-icon v-show="model?.includes(option)" icon="check" />
|
||||
{{ labels?.get(option) ?? option }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<CreateTagModal ref="createTagModal" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.input-disabled {
|
||||
background-color: gray !important;
|
||||
opacity: 0.2;
|
||||
|
||||
.input {
|
||||
background-color: gray !important;
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
.btn.btn-disabled {
|
||||
background-color: gray;
|
||||
opacity: 0.2;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import RandomConfigurationPanel from "./RandomConfigurationPanel.vue";
|
||||
|
||||
const opened = ref<boolean>(false);
|
||||
|
||||
function open() {
|
||||
opened.value = true;
|
||||
}
|
||||
|
||||
function close() {
|
||||
opened.value = false;
|
||||
emit("closed");
|
||||
}
|
||||
|
||||
function onPreferencesSaved() {
|
||||
close();
|
||||
}
|
||||
|
||||
const emit = defineEmits({
|
||||
closed: () => true,
|
||||
});
|
||||
defineExpose({ open, close });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="drawer drawer-end z-[10000]">
|
||||
<input id="my-drawer" type="checkbox" class="drawer-toggle" :checked="opened" />
|
||||
<div class="drawer-side w-full">
|
||||
<label @click.prevent="close" for="my-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||
<div class="menu p-4 space-y-4 w-full lg:w-2/6 min-h-full bg-base-200 text-base-content">
|
||||
<div class="flex flex-row w-full">
|
||||
<font-awesome-icon
|
||||
@click="close"
|
||||
class="btn btn-circle btn-sm text-gray-500 bg-transparent"
|
||||
icon="fa-solid fa-xmark"
|
||||
/>
|
||||
<h3 class="text-lg font-bold ml-5 self-center">Configure filters</h3>
|
||||
<div class="flex-1"></div>
|
||||
</div>
|
||||
|
||||
<RandomConfigurationPanel @preferences-saved="onPreferencesSaved" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
<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 Restaurant from "../models/restaurant";
|
||||
import { filterValues } from "../utils/filters";
|
||||
|
||||
const priceTicks = {
|
||||
1: "€",
|
||||
2: "€€",
|
||||
3: "€€€",
|
||||
4: "€€€€",
|
||||
};
|
||||
|
||||
const { restaurants } = storeToRefs(useRestaurantsStore());
|
||||
|
||||
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 tagsNames = computed(() => {
|
||||
return availableTags.value.map((tag) => tag.name);
|
||||
});
|
||||
|
||||
const { values, 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()),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
const filteredRestaurants = ref<number>(restaurants.value.length);
|
||||
|
||||
watch(values, async (newval, oldval) => {
|
||||
const filters: ((_: Restaurant) => boolean)[] = [];
|
||||
|
||||
if (values.excludeRestaurantsEnabled && values.excludedRestaurants) {
|
||||
filters.push(filterValues.bind(undefined, "id", values.excludedRestaurants, true));
|
||||
}
|
||||
|
||||
filteredRestaurants.value = useRestaurantsStore().getFilteredRestaurants(filters).length;
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
function onSavePreferences() {
|
||||
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"
|
||||
strict
|
||||
/>
|
||||
</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="tagsNames" :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="tagsNames" :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>
|
||||
{{ filteredRestaurants }} filtered restaurants
|
||||
<button @click="onSavePreferences" class="btn btn-outline w-full">Save preferences</button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -3,100 +3,37 @@ import { ref } from "vue";
|
|||
import { type PropType } from "vue";
|
||||
|
||||
import CreateTagModal from "./CreateTagModal.vue";
|
||||
import MultiSelectField from "./MultiSelectField.vue";
|
||||
|
||||
const props = defineProps({
|
||||
options: { type: Array as PropType<string[]> },
|
||||
labels: { type: Map as PropType<Map<string, string>>, default: () => {} },
|
||||
clearable: { type: Boolean, default: true },
|
||||
create: { type: Boolean, default: true },
|
||||
disabled: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const model = defineModel<string[]>();
|
||||
|
||||
const emit = defineEmits(["updateModelValue"]);
|
||||
|
||||
const query = ref<string>("");
|
||||
|
||||
const createTagModal = ref<typeof CreateTagModal>();
|
||||
|
||||
function onBadgeClicked(value: string) {
|
||||
if (model.value?.includes(value)) {
|
||||
model.value = model.value.filter((val) => val != value);
|
||||
}
|
||||
}
|
||||
|
||||
function onOptionClicked(value: string) {
|
||||
if (model.value === undefined) {
|
||||
model.value = [value];
|
||||
} else if (model.value?.includes(value)) {
|
||||
model.value = model.value?.filter((val) => val !== value);
|
||||
} else {
|
||||
model.value = [...model.value, value];
|
||||
}
|
||||
|
||||
query.value = "";
|
||||
}
|
||||
|
||||
function showClear() {
|
||||
if (!props.clearable) {
|
||||
return false;
|
||||
}
|
||||
if (Array.isArray(model.value) && model.value?.length == 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function onClear() {
|
||||
model.value = [];
|
||||
}
|
||||
|
||||
function onCreateNewTag() {
|
||||
createTagModal.value?.show();
|
||||
}
|
||||
|
||||
function filteredOptions() {
|
||||
if (!query.value) return props.options;
|
||||
const lcquery = query.value.toLocaleLowerCase();
|
||||
return props.options?.filter((opt) => opt.toLocaleLowerCase().startsWith(lcquery));
|
||||
if (props.create) {
|
||||
createTagModal.value?.show();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="dropdown" class="w-full combobox dropdown">
|
||||
<div
|
||||
class="input input-bordered flex h-fit min-h-[3rem] w-full flex-row items-stretch px-0 focus-within:outline focus-within:outline-2 focus-within:outline-offset-2"
|
||||
>
|
||||
<div class="mx-2 flex flex-wrap items-center">
|
||||
<div v-for="(value, idx) in model" :key="`${value}-${idx}`" class="badge m-1">
|
||||
<button class="mr-2" @click="onBadgeClicked(value)">
|
||||
<font-awesome-icon icon="xmark" />
|
||||
</button>
|
||||
{{ labels?.get(value) ?? value }}
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
ref="input"
|
||||
v-model="query"
|
||||
class="input input-ghost h-auto min-w-[8ch] flex-1 border-0 p-0 focus:outline-0"
|
||||
role="combobox"
|
||||
type="text"
|
||||
/>
|
||||
<button v-show="showClear" class="btn btn-ghost h-auto self-stretch" @click="onClear">
|
||||
<font-awesome-icon icon="xmark" />
|
||||
</button>
|
||||
</div>
|
||||
<ul
|
||||
class="menu flex-nowrap dropdown-content overflow-y-auto z-10 max-h-96 w-full rounded-lg border border-neutral-content bg-base-100 p-0 shadow"
|
||||
>
|
||||
<li><button @click="onCreateNewTag">Create new tag...</button></li>
|
||||
<li v-for="option in filteredOptions()" :key="option" role="option" class="m-0 p-0">
|
||||
<button :class="{ active: model?.includes(option) }" @click="onOptionClicked(option)">
|
||||
<font-awesome-icon v-show="model?.includes(option)" icon="check" />
|
||||
{{ option }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<MultiSelectField
|
||||
v-model="model"
|
||||
:options="props.options"
|
||||
:labels="props.labels"
|
||||
:clearable="props.clearable"
|
||||
:disabled="props.disabled"
|
||||
>
|
||||
<li v-if="props.create"><button @click="onCreateNewTag">Create new tag...</button></li>
|
||||
</MultiSelectField>
|
||||
|
||||
<CreateTagModal ref="createTagModal" />
|
||||
</template>
|
||||
|
|
|
|||
10
src/main.ts
10
src/main.ts
|
|
@ -1,5 +1,10 @@
|
|||
import "./style.css";
|
||||
|
||||
// Vuetify
|
||||
import { createVuetify } from "vuetify";
|
||||
// import VRangeSlider from "vuetify/components";
|
||||
import * as components from "vuetify/components";
|
||||
|
||||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
|
||||
|
|
@ -13,10 +18,15 @@ library.add(faBars, faUser, faXmark, faCheck, faGitAlt, faExclamationCircle);
|
|||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
|
||||
const vuetify = createVuetify({
|
||||
components,
|
||||
});
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(createPinia());
|
||||
app.use(router);
|
||||
app.use(vuetify);
|
||||
|
||||
app.component("font-awesome-icon", FontAwesomeIcon);
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,31 @@ export const useRestaurantsStore = defineStore("restaurants", {
|
|||
}),
|
||||
getters: {
|
||||
getRandomRestaurant(state) {
|
||||
return () => _.sample(state.restaurants);
|
||||
return (filters: ((_: Restaurant) => boolean)[]) => {
|
||||
const filtered = state.restaurants.filter((restaurant) => {
|
||||
for (const filter of filters) {
|
||||
if (!filter(restaurant)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return _.sample(filtered);
|
||||
};
|
||||
},
|
||||
getFilteredRestaurants(state) {
|
||||
return (filters: ((_: Restaurant) => boolean)[]) => {
|
||||
const filtered = state.restaurants.filter((restaurant) => {
|
||||
console.log(restaurant);
|
||||
for (const filter of filters) {
|
||||
if (!filter(restaurant)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return filtered;
|
||||
};
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
export function filterValues(field: string, values: any[], negative: boolean = false, obj: Object) {
|
||||
console.log("fielf", field, "values", values, "negative", negative, "obj", obj);
|
||||
if (negative) {
|
||||
return !values.includes(obj[field]);
|
||||
} else {
|
||||
return values.includes(obj[field]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,25 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import "@/assets/form.css";
|
||||
|
||||
import RandomConfigurationDrawer from "../components/RandomConfigurationDrawer.vue";
|
||||
|
||||
import { useRestaurantsStore } from "../stores/restaurants.ts";
|
||||
import Restaurant from "../models/restaurant.ts";
|
||||
|
||||
const restaurant = ref<string>();
|
||||
const restaurant = ref<Restaurant>();
|
||||
|
||||
const store = useRestaurantsStore();
|
||||
|
||||
function getRandomRestaurant() {
|
||||
restaurant.value = store.getRandomRestaurant();
|
||||
}
|
||||
|
||||
const drawer = ref<typeof RandomConfigurationDrawer>();
|
||||
|
||||
function onConfigureClicked() {
|
||||
drawer.value?.open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -18,4 +28,14 @@ function getRandomRestaurant() {
|
|||
<p class="">{{ restaurant?.name }}</p>
|
||||
<button class="btn btn-outline btn-primary mt-10" @click="getRandomRestaurant">Get random restaurant</button>
|
||||
</div>
|
||||
|
||||
<button class="btn" @click="onConfigureClicked">Configure</button>
|
||||
|
||||
<RandomConfigurationDrawer ref="drawer" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.btn {
|
||||
@apply border-solid;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue