Working on list view and reviews
parent
29f8902f26
commit
d0cb6eeca9
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"semi": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": false,
|
||||||
|
"printWidth": 120,
|
||||||
|
"trailingComma": "es5"
|
||||||
|
}
|
||||||
|
|
@ -8,12 +8,18 @@
|
||||||
"name": "doxfood-frontend",
|
"name": "doxfood-frontend",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^6.5.2",
|
||||||
|
"@fortawesome/vue-fontawesome": "^3.0.8",
|
||||||
"@vee-validate/yup": "^4.13.0",
|
"@vee-validate/yup": "^4.13.0",
|
||||||
"daisyui": "^4.12.2",
|
"daisyui": "^4.12.2",
|
||||||
|
"datatables.net-responsive": "^3.0.2",
|
||||||
|
"datatables.net-select": "^2.0.3",
|
||||||
"datatables.net-vue3": "^3.0.1",
|
"datatables.net-vue3": "^3.0.1",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"pocketbase": "^0.21.3",
|
"pocketbase": "^0.21.3",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.6.2",
|
||||||
"vee-validate": "^4.13.0",
|
"vee-validate": "^4.13.0",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
"vue-router": "^4.3.2",
|
"vue-router": "^4.3.2",
|
||||||
|
|
@ -418,6 +424,48 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fortawesome/fontawesome-common-types": {
|
||||||
|
"version": "6.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz",
|
||||||
|
"integrity": "sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fortawesome/fontawesome-svg-core": {
|
||||||
|
"version": "6.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.2.tgz",
|
||||||
|
"integrity": "sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==",
|
||||||
|
"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",
|
||||||
|
"integrity": "sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-common-types": "6.5.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fortawesome/vue-fontawesome": {
|
||||||
|
"version": "3.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.8.tgz",
|
||||||
|
"integrity": "sha512-yyHHAj4G8pQIDfaIsMvQpwKMboIZtcHTUvPqXjOHyldh1O1vZfH4W03VDPv5RvI9P6DLTzJQlmVgj9wCf7c2Fw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@fortawesome/fontawesome-svg-core": "~1 || ~6",
|
||||||
|
"vue": ">= 3.0.0 < 4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@isaacs/cliui": {
|
"node_modules/@isaacs/cliui": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||||
|
|
@ -1176,11 +1224,28 @@
|
||||||
"version": "2.0.8",
|
"version": "2.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-2.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-2.0.8.tgz",
|
||||||
"integrity": "sha512-4/2dYx4vl975zQqZbyoVEm0huPe61qffjBRby7K7V+y9E+ORq4R8KavkgrNMmIgO6cl85Pg4AvCbVjvPCIT1Yg==",
|
"integrity": "sha512-4/2dYx4vl975zQqZbyoVEm0huPe61qffjBRby7K7V+y9E+ORq4R8KavkgrNMmIgO6cl85Pg4AvCbVjvPCIT1Yg==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jquery": ">=1.7"
|
"jquery": ">=1.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/datatables.net-responsive": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/datatables.net-responsive/-/datatables.net-responsive-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-OVsrTfcYvT8YtnOeCef51veKc+DABVPWOypvID+xFzCgyBDCIgcauTk+H5zItOscXw/CwSIbGN9zD6Sxa7lZPA==",
|
||||||
|
"dependencies": {
|
||||||
|
"datatables.net": "^2",
|
||||||
|
"jquery": ">=1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/datatables.net-select": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/datatables.net-select/-/datatables.net-select-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-Fw7/uAlpOTfz8R8JAXDV4qHfP2MA+4vnNSYjuo9NVbkwrA/71t+KGN2JRugCTTzJuiyQ8YN6thzC9lPBqd/7nA==",
|
||||||
|
"dependencies": {
|
||||||
|
"datatables.net": "^2",
|
||||||
|
"jquery": ">=1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/datatables.net-vue3": {
|
"node_modules/datatables.net-vue3": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/datatables.net-vue3/-/datatables.net-vue3-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/datatables.net-vue3/-/datatables.net-vue3-3.0.1.tgz",
|
||||||
|
|
@ -1537,8 +1602,7 @@
|
||||||
"node_modules/jquery": {
|
"node_modules/jquery": {
|
||||||
"version": "3.7.1",
|
"version": "3.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
|
||||||
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
|
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/leaflet": {
|
"node_modules/leaflet": {
|
||||||
"version": "1.9.4",
|
"version": "1.9.4",
|
||||||
|
|
@ -1961,6 +2025,94 @@
|
||||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/prettier": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==",
|
||||||
|
"peer": true,
|
||||||
|
"bin": {
|
||||||
|
"prettier": "bin/prettier.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prettier-plugin-tailwindcss": {
|
||||||
|
"version": "0.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.2.tgz",
|
||||||
|
"integrity": "sha512-eFefm4cg+1c2B57+H274Qm//CTWBdtQN9ansl0YTP/8TC8x3bugCTQSS/e4FC5Ctl9djhTzsbcMrZ7x2/abIow==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@ianvs/prettier-plugin-sort-imports": "*",
|
||||||
|
"@prettier/plugin-pug": "*",
|
||||||
|
"@shopify/prettier-plugin-liquid": "*",
|
||||||
|
"@trivago/prettier-plugin-sort-imports": "*",
|
||||||
|
"@zackad/prettier-plugin-twig-melody": "*",
|
||||||
|
"prettier": "^3.0",
|
||||||
|
"prettier-plugin-astro": "*",
|
||||||
|
"prettier-plugin-css-order": "*",
|
||||||
|
"prettier-plugin-import-sort": "*",
|
||||||
|
"prettier-plugin-jsdoc": "*",
|
||||||
|
"prettier-plugin-marko": "*",
|
||||||
|
"prettier-plugin-organize-attributes": "*",
|
||||||
|
"prettier-plugin-organize-imports": "*",
|
||||||
|
"prettier-plugin-sort-imports": "*",
|
||||||
|
"prettier-plugin-style-order": "*",
|
||||||
|
"prettier-plugin-svelte": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@ianvs/prettier-plugin-sort-imports": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@prettier/plugin-pug": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@shopify/prettier-plugin-liquid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@trivago/prettier-plugin-sort-imports": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@zackad/prettier-plugin-twig-melody": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-astro": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-css-order": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-import-sort": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-jsdoc": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-marko": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-organize-attributes": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-organize-imports": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-sort-imports": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-style-order": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"prettier-plugin-svelte": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/property-expr": {
|
"node_modules/property-expr": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,18 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^6.5.2",
|
||||||
|
"@fortawesome/vue-fontawesome": "^3.0.8",
|
||||||
"@vee-validate/yup": "^4.13.0",
|
"@vee-validate/yup": "^4.13.0",
|
||||||
"daisyui": "^4.12.2",
|
"daisyui": "^4.12.2",
|
||||||
|
"datatables.net-responsive": "^3.0.2",
|
||||||
|
"datatables.net-select": "^2.0.3",
|
||||||
"datatables.net-vue3": "^3.0.1",
|
"datatables.net-vue3": "^3.0.1",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"pocketbase": "^0.21.3",
|
"pocketbase": "^0.21.3",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.6.2",
|
||||||
"vee-validate": "^4.13.0",
|
"vee-validate": "^4.13.0",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
"vue-router": "^4.3.2",
|
"vue-router": "^4.3.2",
|
||||||
|
|
|
||||||
|
|
@ -32,3 +32,11 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dataTable {
|
||||||
|
.selected {
|
||||||
|
@apply bg-gray-300;
|
||||||
|
@apply hover:bg-gray-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
#map {
|
||||||
|
height: 90vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width < 640px) {
|
||||||
|
#map {
|
||||||
|
height: 50vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,36 +4,61 @@ import { currentUser, signOut } from "../pocketbase";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div class="navbar">
|
||||||
class="navbar w-full bg-base-100 sm:border-b sm:border-b-neutral-200 flex flex-row place-content-center"
|
<div class="navbar-start">
|
||||||
>
|
<div class="dropdown">
|
||||||
<div class="flex-1 flex-row">
|
<div tabindex="0" role="button" class="btn btn-ghost btn-circle lg:hidden">
|
||||||
<RouterLink class="btn btn-ghost" to="/">Home</RouterLink>
|
<font-awesome-icon icon="fa-solid fa-bars" />
|
||||||
<RouterLink class="btn btn-ghost" to="/about">About</RouterLink>
|
</div>
|
||||||
<RouterLink class="btn btn-ghost" to="/random">Random</RouterLink>
|
<ul
|
||||||
<RouterLink class="btn btn-ghost" to="/list">List</RouterLink>
|
tabindex="0"
|
||||||
<RouterLink class="btn btn-ghost" to="/create"
|
class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52"
|
||||||
>Create Restaurant</RouterLink
|
>
|
||||||
>
|
<li><RouterLink class="btn btn-ghost" to="/">Home</RouterLink></li>
|
||||||
</div>
|
<li>
|
||||||
<div class="flex-1"></div>
|
<RouterLink class="btn btn-ghost" to="/about">About</RouterLink>
|
||||||
<div class="flex-none">
|
</li>
|
||||||
<div class="flex flew-row" v-if="currentUser">
|
<li>
|
||||||
<label>
|
<RouterLink class="btn btn-ghost" to="/random">Random</RouterLink>
|
||||||
Logged in as {{ currentUser.username }}
|
</li>
|
||||||
<button
|
<li>
|
||||||
v-if="currentUser"
|
<RouterLink class="btn btn-ghost" to="/list">List</RouterLink>
|
||||||
@click="signOut"
|
</li>
|
||||||
class="btn btn-ghost text-red-600"
|
<li>
|
||||||
>
|
<RouterLink class="btn btn-ghost" to="/create">Create Restaurant</RouterLink>
|
||||||
Sign Out
|
</li>
|
||||||
</button>
|
</ul>
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<RouterLink v-else class="btn btn-ghost" to="/login">
|
<div class="navbar-center hidden lg:flex lg:flex-row">
|
||||||
Sign In
|
<div class="menu menu-horizontal px-1">
|
||||||
</RouterLink>
|
<RouterLink class="btn btn-ghost" to="/">Home</RouterLink>
|
||||||
|
<RouterLink class="btn btn-ghost" to="/about">About</RouterLink>
|
||||||
|
<RouterLink class="btn btn-ghost" to="/random">Random</RouterLink>
|
||||||
|
<RouterLink class="btn btn-ghost" to="/list">List</RouterLink>
|
||||||
|
<RouterLink class="btn btn-ghost" to="/create"
|
||||||
|
>Create Restaurant</RouterLink
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-end">
|
||||||
|
<div class="dropdown dropdown-end">
|
||||||
|
<div tabindex="0" role="button" class="btn btn-ghost btn-circle">
|
||||||
|
<font-awesome-icon icon="fa-solid fa-user" />
|
||||||
|
</div>
|
||||||
|
<ul
|
||||||
|
tabindex="0"
|
||||||
|
class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-40"
|
||||||
|
>
|
||||||
|
<li v-if="currentUser" class="mb-2">Logged in as {{ currentUser.username }}</li>
|
||||||
|
<li v-if="currentUser" @click="signOut" class="text-red-600 btn btn-xs">Sign Out</li>
|
||||||
|
<li v-else>
|
||||||
|
<RouterLink class="text-current" to="/login">
|
||||||
|
Sign In
|
||||||
|
</RouterLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,40 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from "vue";
|
import { ref, onMounted, PropType, watch } from "vue";
|
||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
import * as L from "leaflet";
|
import * as L from "leaflet";
|
||||||
|
import "../assets/map.css";
|
||||||
|
|
||||||
const initialMap = ref(null);
|
const initialMap = ref(null);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
restaurants: { type: Array as PropType<Object[]> },
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initialMap.value = L.map("map").setView([23.8041, 90.4152], 6);
|
initialMap.value = L.map("map").setView([48.8363012, 2.240709935], 14);
|
||||||
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||||
maxZoom: 19,
|
maxZoom: 19,
|
||||||
attribution:
|
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||||
'© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
|
||||||
}).addTo(initialMap.value);
|
}).addTo(initialMap.value);
|
||||||
|
|
||||||
|
initialMap.value.on("click", function (e) {
|
||||||
|
console.log(e.latlng);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.restaurants,
|
||||||
|
async (newval, oldval) => {
|
||||||
|
props.restaurants?.forEach((restaurant) => {
|
||||||
|
new L.marker({ lat: 22.353953049824614, lng: 86.0591123082797 }).addTo(initialMap);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div id="map" style="height: 90vh"></div>
|
<div @click="onClick" id="map"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import "../assets/datatable.css";
|
||||||
|
|
||||||
import DataTable from "datatables.net-vue3";
|
import DataTable from "datatables.net-vue3";
|
||||||
import DataTablesCore from "datatables.net";
|
import DataTablesCore from "datatables.net";
|
||||||
|
import "datatables.net-select";
|
||||||
import { PropType } from "vue";
|
import { PropType } from "vue";
|
||||||
|
|
||||||
DataTable.use(DataTablesCore);
|
DataTable.use(DataTablesCore);
|
||||||
|
|
@ -14,7 +15,16 @@ const props = defineProps({
|
||||||
const columns = [
|
const columns = [
|
||||||
{ data: "name", title: "Name" },
|
{ data: "name", title: "Name" },
|
||||||
{ data: "tags", title: "Tags" },
|
{ data: "tags", title: "Tags" },
|
||||||
|
{ data: "average_rating", title: "" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function onRowSelected(e, dt, type, indexes) {
|
||||||
|
if (indexes.length != 0 && props.data !== undefined) {
|
||||||
|
emit("restaurantSelected", props.data[indexes[0]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits(["restaurantSelected"]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -31,6 +41,20 @@ const columns = [
|
||||||
bottomStart: null,
|
bottomStart: null,
|
||||||
bottomEnd: null,
|
bottomEnd: null,
|
||||||
},
|
},
|
||||||
|
select: {
|
||||||
|
style: 'single',
|
||||||
|
},
|
||||||
}"
|
}"
|
||||||
/>
|
@select="onRowSelected"
|
||||||
|
>
|
||||||
|
<template #column-2="props">
|
||||||
|
<div class="rating">
|
||||||
|
<input type="radio" disabled :name="`rating-${props.rowIndex}`" class="mask mask-star" :checked="false" />
|
||||||
|
<input type="radio" disabled :name="`rating-${props.rowIndex}`" class="mask mask-star" :checked="false" />
|
||||||
|
<input type="radio" disabled :name="`rating-${props.rowIndex}`" class="mask mask-star" :checked="false" />
|
||||||
|
<input type="radio" disabled :name="`rating-${props.rowIndex}`" class="mask mask-star" :checked="false" />
|
||||||
|
<input type="radio" disabled :name="`rating-${props.rowIndex}`" class="mask mask-star" :checked="false" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
import ReviewsList from "./ReviewsList.vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
restaurant: { type: Object },
|
||||||
|
});
|
||||||
|
|
||||||
|
const opened = ref<boolean>(false);
|
||||||
|
|
||||||
|
function open() {
|
||||||
|
opened.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
opened.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 for="my-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||||
|
<div class="menu p-4 w-full lg:w-2/6 min-h-full bg-base-200 text-base-content">
|
||||||
|
<font-awesome-icon
|
||||||
|
@click="close"
|
||||||
|
class="btn btn-circle btn-sm text-gray-500 bg-transparent"
|
||||||
|
icon="fa-solid fa-xmark"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ReviewsList :restaurant="props.restaurant" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from "vue";
|
||||||
|
import { pb } from "../pocketbase";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
restaurant: { type: Object },
|
||||||
|
});
|
||||||
|
|
||||||
|
const reviews = ref<Object[]>([]);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.restaurant,
|
||||||
|
async (newval, oldval) => {
|
||||||
|
console.log("fetch");
|
||||||
|
if (props.restaurant === undefined) {
|
||||||
|
reviews.value = [];
|
||||||
|
} else {
|
||||||
|
reviews.value = await pb
|
||||||
|
.collection("reviews")
|
||||||
|
.getFullList({ filter: pb.filter("restaurant ~ {:id}", { id: props.restaurant.id }), expand: "user" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<template v-for="review in reviews" :key="review.id">
|
||||||
|
<div class="card card-compact bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title">{{ review.expand.user.username }}</h2>
|
||||||
|
<div class="flex flex-row">
|
||||||
|
<p class="text-left">{{ review.text }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-row items-center">
|
||||||
|
<div class="flex-1"></div>
|
||||||
|
<p class="flex-0 align-bottom flex-grow-0 mx-3">{{ review.rating }}/5</p>
|
||||||
|
<div class="rating align-bottom flex-0 flex-grow-0">
|
||||||
|
<input type="radio" disabled name="rating-1" class="mask mask-star" :checked="false" />
|
||||||
|
<input type="radio" disabled name="rating-1" class="mask mask-star" :checked="false" />
|
||||||
|
<input type="radio" disabled name="rating-1" class="mask mask-star" :checked="true" />
|
||||||
|
<input type="radio" disabled name="rating-1" class="mask mask-star" :checked="false" />
|
||||||
|
<input type="radio" disabled name="rating-1" class="mask mask-star" :checked="false" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
@ -3,6 +3,12 @@ import "./style.css";
|
||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
import { createPinia } from "pinia";
|
import { createPinia } from "pinia";
|
||||||
|
|
||||||
|
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||||
|
import { faBars, faUser, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
|
library.add(faBars, faUser, faXmark);
|
||||||
|
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
|
|
||||||
|
|
@ -11,4 +17,6 @@ const app = createApp(App);
|
||||||
app.use(createPinia());
|
app.use(createPinia());
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
|
app.component("font-awesome-icon", FontAwesomeIcon);
|
||||||
|
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export const average = (array) => array.reduce((a, b) => a + b) / array.length;
|
||||||
|
|
@ -33,13 +33,14 @@ onMounted(async () => {
|
||||||
const tags = await pb.collection("tags").getFullList();
|
const tags = await pb.collection("tags").getFullList();
|
||||||
tagsModel.value = tags.map((record) => record.name);
|
tagsModel.value = tags.map((record) => record.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function onUpdateSelectedTags(selected: string[]) {
|
||||||
|
tagsModel.value = selected;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form
|
<form class="flex flex-col place-content-center items-center h-full" @submit.prevent="onSubmit">
|
||||||
class="flex flex-col place-content-center items-center h-full"
|
|
||||||
@submit.prevent="onSubmit"
|
|
||||||
>
|
|
||||||
<div class="flex flex-row flex-nowrap">
|
<div class="flex flex-row flex-nowrap">
|
||||||
<div class="mx-5 my-4 w-full">
|
<div class="mx-5 my-4 w-full">
|
||||||
<label class="form-label"> Name </label>
|
<label class="form-label"> Name </label>
|
||||||
|
|
@ -55,17 +56,7 @@ onMounted(async () => {
|
||||||
<div class="flex flex-row flex-nowrap">
|
<div class="flex flex-row flex-nowrap">
|
||||||
<div class="mx-5 my-4 w-full">
|
<div class="mx-5 my-4 w-full">
|
||||||
<label class="form-label"> Tags </label>
|
<label class="form-label"> Tags </label>
|
||||||
<MultiSelectField :model="tags" :options="tagsModel" />
|
<MultiSelectField :model="tags" :options="tagsModel" @update-model-value="onUpdateSelectedTags" />
|
||||||
<input
|
|
||||||
:class="
|
|
||||||
errors.cuisine_type === undefined
|
|
||||||
? 'form-field'
|
|
||||||
: 'form-field-error'
|
|
||||||
"
|
|
||||||
v-model="cuisine"
|
|
||||||
v-bind="cuisineAttrs"
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
<label class="text-red-500">{{ errors.cuisine_type }}</label>
|
<label class="text-red-500">{{ errors.cuisine_type }}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -73,9 +64,7 @@ onMounted(async () => {
|
||||||
<div class="mx-5 my-4">
|
<div class="mx-5 my-4">
|
||||||
<label class="form-label"> Price </label>
|
<label class="form-label"> Price </label>
|
||||||
<input
|
<input
|
||||||
:class="
|
:class="errors.price_range === undefined ? 'form-field' : 'form-field-error'"
|
||||||
errors.price_range === undefined ? 'form-field' : 'form-field-error'
|
|
||||||
"
|
|
||||||
v-model="price"
|
v-model="price"
|
||||||
v-bind="priceAttrs"
|
v-bind="priceAttrs"
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
||||||
|
|
@ -3,22 +3,50 @@ import { onMounted, ref } from "vue";
|
||||||
import { pb } from "../pocketbase";
|
import { pb } from "../pocketbase";
|
||||||
import RestaurantsTable from "../components/RestaurantsTable.vue";
|
import RestaurantsTable from "../components/RestaurantsTable.vue";
|
||||||
import RestaurantsMap from "../components/RestaurantsMap.vue";
|
import RestaurantsMap from "../components/RestaurantsMap.vue";
|
||||||
|
import ReviewsDrawer from "../components/ReviewsDrawer.vue";
|
||||||
|
import { average } from "../utils/average";
|
||||||
|
|
||||||
const data = ref<Object[]>([]);
|
const data = ref<Object[]>([]);
|
||||||
|
|
||||||
|
const drawer = ref<typeof ReviewsDrawer>();
|
||||||
|
|
||||||
|
const selectedRestaurant = ref<Object>();
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const restaurants = await pb.collection("restaurants").getFullList();
|
const restaurants = await pb.collection("restaurants").getFullList({ expand: "reviews_via_restaurant" });
|
||||||
|
|
||||||
|
restaurants.forEach((restaurant) => {
|
||||||
|
const ratings: number[] = [];
|
||||||
|
restaurant.expand?.reviews_via_restaurant.forEach((review) => {
|
||||||
|
ratings.push(review.rating);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ratings.length !== 0) {
|
||||||
|
restaurant.average_rating = average(ratings);
|
||||||
|
} else {
|
||||||
|
restaurant.average_rating = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(restaurants);
|
||||||
data.value = restaurants;
|
data.value = restaurants;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function onRestaurantSelected(restaurant: Object) {
|
||||||
|
selectedRestaurant.value = restaurant;
|
||||||
|
drawer.value?.open();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col-reverse sm:flex-row">
|
<div class="flex flex-col-reverse sm:flex-row">
|
||||||
<div class="w-full flex-1">
|
<div class="w-full flex-1">
|
||||||
<RestaurantsTable :data="data" />
|
<RestaurantsTable @restaurant-selected="onRestaurantSelected" :data="data" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1 h-3/6 lg:h-full">
|
||||||
<RestaurantsMap />
|
<RestaurantsMap />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ReviewsDrawer :restaurant="selectedRestaurant" ref="drawer" />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue