diff --git a/package-lock.json b/package-lock.json index ebdc6c1..1305ea6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index aebb7e3..8a452d8 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/assets/form.css b/src/assets/form.css index 8dd45c2..4c72b57 100644 --- a/src/assets/form.css +++ b/src/assets/form.css @@ -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; diff --git a/src/components/MultiSelectField.vue b/src/components/MultiSelectField.vue new file mode 100644 index 0000000..0bb3d78 --- /dev/null +++ b/src/components/MultiSelectField.vue @@ -0,0 +1,123 @@ + + + + + + + + + + + {{ labels?.get(value) ?? value }} + + + + + + + + + + + + + {{ labels?.get(option) ?? option }} + + + + + + + + + diff --git a/src/components/RandomConfigurationDrawer.vue b/src/components/RandomConfigurationDrawer.vue new file mode 100644 index 0000000..3568aaf --- /dev/null +++ b/src/components/RandomConfigurationDrawer.vue @@ -0,0 +1,46 @@ + + + + + + + + + + + Configure filters + + + + + + + + diff --git a/src/components/RandomConfigurationPanel.vue b/src/components/RandomConfigurationPanel.vue new file mode 100644 index 0000000..ce05080 --- /dev/null +++ b/src/components/RandomConfigurationPanel.vue @@ -0,0 +1,172 @@ + + + + + + + + + Price range + + + + + + + + + Include tags + + + + + + + + + Exclude tags + + + + + + + + + Include restaurants + + + + + + + + + Exclude restaurants + + + + + + + + {{ filteredRestaurants }} filtered restaurants + Save preferences + + diff --git a/src/components/TagsField.vue b/src/components/TagsField.vue index 632060e..9cf6623 100644 --- a/src/components/TagsField.vue +++ b/src/components/TagsField.vue @@ -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 }, labels: { type: Map as PropType>, default: () => {} }, clearable: { type: Boolean, default: true }, + create: { type: Boolean, default: true }, + disabled: { type: Boolean, default: false }, }); const model = defineModel(); -const emit = defineEmits(["updateModelValue"]); - -const query = ref(""); - const createTagModal = ref(); -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(); + } } - - - - - - - - {{ labels?.get(value) ?? value }} - - - - - - - - - Create new tag... - - - - {{ option }} - - - - + + Create new tag... + diff --git a/src/main.ts b/src/main.ts index 4f13407..ca08b15 100644 --- a/src/main.ts +++ b/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); diff --git a/src/stores/restaurants.ts b/src/stores/restaurants.ts index 39bf254..4c08522 100644 --- a/src/stores/restaurants.ts +++ b/src/stores/restaurants.ts @@ -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: { diff --git a/src/utils/filters.ts b/src/utils/filters.ts new file mode 100644 index 0000000..722c5f4 --- /dev/null +++ b/src/utils/filters.ts @@ -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]); + } +} diff --git a/src/views/RandomView.vue b/src/views/RandomView.vue index a9997f0..8287f7a 100644 --- a/src/views/RandomView.vue +++ b/src/views/RandomView.vue @@ -1,15 +1,25 @@ @@ -18,4 +28,14 @@ function getRandomRestaurant() { {{ restaurant?.name }} Get random restaurant + + Configure + + + +
{{ restaurant?.name }}