From 9282cde499dd49622620024683c8836628f7500f Mon Sep 17 00:00:00 2001 From: Eloi Zalczer Date: Wed, 12 Jun 2024 18:12:59 +0200 Subject: [PATCH] Improvements to reviews and creation form --- package-lock.json | 13 ++++ package.json | 1 + src/components/MultiSelectField.vue | 62 +++++----------- src/components/NavigationBar.vue | 47 +++++-------- src/components/RatingField.vue | 105 ++++++++++++++++++++++++++++ src/components/RestaurantsTable.vue | 16 +++-- src/components/ReviewField.vue | 61 ++++++++++++++++ src/components/ReviewsDrawer.vue | 25 ++++++- src/components/ReviewsList.vue | 42 +++++++---- src/main.ts | 5 +- src/utils/average.ts | 1 - src/utils/math.ts | 6 ++ src/views/AboutView.vue | 3 + src/views/CreateView.vue | 82 ++++++++++++++++------ src/views/ListView.vue | 12 +++- 15 files changed, 358 insertions(+), 123 deletions(-) create mode 100644 src/components/RatingField.vue create mode 100644 src/components/ReviewField.vue delete mode 100644 src/utils/average.ts create mode 100644 src/utils/math.ts diff --git a/package-lock.json b/package-lock.json index 56bf1f6..e824341 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.5.2", + "@fortawesome/free-brands-svg-icons": "^6.5.2", "@fortawesome/free-solid-svg-icons": "^6.5.2", "@fortawesome/vue-fontawesome": "^3.0.8", "@vee-validate/yup": "^4.13.0", @@ -445,6 +446,18 @@ "node": ">=6" } }, + "node_modules/@fortawesome/free-brands-svg-icons": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.2.tgz", + "integrity": "sha512-zi5FNYdmKLnEc0jc0uuHH17kz/hfYTg4Uei0wMGzcoCL/4d3WM3u1VMc0iGGa31HuhV5i7ZK8ZlTCQrHqRHSGQ==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.5.2" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@fortawesome/free-solid-svg-icons": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz", diff --git a/package.json b/package.json index 2b5ee2e..11456f9 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.5.2", + "@fortawesome/free-brands-svg-icons": "^6.5.2", "@fortawesome/free-solid-svg-icons": "^6.5.2", "@fortawesome/vue-fontawesome": "^3.0.8", "@vee-validate/yup": "^4.13.0", diff --git a/src/components/MultiSelectField.vue b/src/components/MultiSelectField.vue index 4a0c3f5..3415b50 100644 --- a/src/components/MultiSelectField.vue +++ b/src/components/MultiSelectField.vue @@ -3,61 +3,53 @@ import { ref } from "vue"; import { type PropType } from "vue"; const props = defineProps({ - model: { type: Array as PropType }, options: { type: Array as PropType }, labels: { type: Map as PropType>, default: () => {} }, clearable: { type: Boolean, default: true }, }); +const model = defineModel(); + const emit = defineEmits(["updateModelValue"]); const query = ref(""); function onBadgeClicked(value: string) { - if (props.model === undefined) { - return; + if (model.value?.includes(value)) { + model.value = model.value.filter((val) => val != value); } - - const newval = props.model.includes(value) - ? props.model.filter((val) => val != value) - : [...props.model, value]; - - emit("updateModelValue", newval); } function onOptionClicked(value: string) { - if (props.model === undefined) { - return; + 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]; } - const newval = props.model?.includes(value) - ? props.model?.filter((val) => val !== value) - : [...props.model, value]; - query.value = ""; - emit("updateModelValue", newval); } function showClear() { if (!props.clearable) { return false; } - if (Array.isArray(props.model) && props.model?.length == 0) { + if (Array.isArray(model.value) && model.value?.length == 0) { return false; } return true; } function onClear() { - emit("updateModelValue", []); + model.value = []; } function filteredOptions() { if (!query.value) return props.options; const lcquery = query.value.toLocaleLowerCase(); - return props.options?.filter((opt) => - opt.toLocaleLowerCase().startsWith(lcquery) - ); + return props.options?.filter((opt) => opt.toLocaleLowerCase().startsWith(lcquery)); } @@ -67,11 +59,7 @@ function filteredOptions() { 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" >
-
+
@@ -85,27 +73,13 @@ function filteredOptions() { role="combobox" type="text" /> -
-
diff --git a/src/components/RatingField.vue b/src/components/RatingField.vue new file mode 100644 index 0000000..9ff7790 --- /dev/null +++ b/src/components/RatingField.vue @@ -0,0 +1,105 @@ + + + diff --git a/src/components/RestaurantsTable.vue b/src/components/RestaurantsTable.vue index efa030f..3e7a708 100644 --- a/src/components/RestaurantsTable.vue +++ b/src/components/RestaurantsTable.vue @@ -5,6 +5,7 @@ import DataTable from "datatables.net-vue3"; import DataTablesCore from "datatables.net"; import "datatables.net-select"; import { PropType } from "vue"; +import RatingField from "./RatingField.vue"; DataTable.use(DataTablesCore); @@ -48,13 +49,14 @@ const emit = defineEmits(["restaurantSelected"]); @select="onRowSelected" > diff --git a/src/components/ReviewField.vue b/src/components/ReviewField.vue new file mode 100644 index 0000000..1658f7a --- /dev/null +++ b/src/components/ReviewField.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/src/components/ReviewsDrawer.vue b/src/components/ReviewsDrawer.vue index db75a89..d9260b0 100644 --- a/src/components/ReviewsDrawer.vue +++ b/src/components/ReviewsDrawer.vue @@ -1,12 +1,16 @@ @@ -24,14 +44,15 @@ defineExpose({ open, close });
-
diff --git a/src/components/ReviewsList.vue b/src/components/ReviewsList.vue index 832b8f2..ca817cc 100644 --- a/src/components/ReviewsList.vue +++ b/src/components/ReviewsList.vue @@ -1,6 +1,7 @@ diff --git a/src/views/CreateView.vue b/src/views/CreateView.vue index 25c4998..3bf7978 100644 --- a/src/views/CreateView.vue +++ b/src/views/CreateView.vue @@ -6,42 +6,74 @@ import "@/assets/form.css"; import { pb } from "../pocketbase.ts"; import MultiSelectField from "../components/MultiSelectField.vue"; -import { onMounted, ref } from "vue"; +import { computed, onMounted, ref } from "vue"; const { meta, errors, handleSubmit, defineField, resetForm } = useForm({ validationSchema: toTypedSchema( yup.object({ name: yup.string().required("This field is required."), - tags: yup.string().required("This field is required."), - price_range: yup.string().required("This field is required."), + tags: yup.array().of(yup.string()).required("This field is required."), + price: yup.string().required("This field is required."), }) ), }); const onSubmit = handleSubmit((values) => { - pb.collection("restaurants").create(values); + const tags: Object[] = []; + + values.tags.forEach((name) => { + const tag = availableTags.value.filter((tag) => tag.name === name)[0]; + if (tag !== undefined) { + tags.push(tag.id); + } + }); + + try { + pb.collection("restaurants").create({ + name: values.name, + price_range: values.price, + tags: tags, + }); + + successMessage.value = `Restaurant ${values.name} created successfully!`; + errorMessage.value = undefined; + } catch (err) { + errorMessage.value = err; + successMessage.value = undefined; + } resetForm(); }); -const tagsModel = ref([]); +const errorMessage = ref(); +const successMessage = ref(); + +const availableTags = ref([]); + +const tagsNames = computed(() => { + return availableTags.value.map((tag) => tag.name); +}); const [name, nameAttrs] = defineField("name"); const [tags, tagsAttrs] = defineField("tags"); -const [price, priceAttrs] = defineField("price_range"); +const [price, priceAttrs] = defineField("price"); onMounted(async () => { - const tags = await pb.collection("tags").getFullList(); - tagsModel.value = tags.map((record) => record.name); + availableTags.value = await pb.collection("tags").getFullList(); }); - -function onUpdateSelectedTags(selected: string[]) { - tagsModel.value = selected; -}