Merge pull request 'Überarbeitung Suchansicht' (#37) from feature-search into main

Reviewed-on: kompetenzinventar/ki-frontend#37
This commit is contained in:
scammo 2021-09-23 16:57:31 +02:00
commit a2048d0eb9
27 changed files with 404 additions and 176 deletions

View File

@ -11,7 +11,7 @@ Files: .browserslistrc .dockerignore .eslintrc.js .gitignore
Copyright: WTF Kooperative eG <https://wtf-eg.de/> Copyright: WTF Kooperative eG <https://wtf-eg.de/>
License: AGPL-3.0-or-later License: AGPL-3.0-or-later
Files: src/assets/img/wtf_logo* Files: src/assets/img/wtf*
Copyright: WTF Kooperative eG <https://wtf-eg.de/> Copyright: WTF Kooperative eG <https://wtf-eg.de/>
License: LicenseRef-WTF License: LicenseRef-WTF

View File

@ -1,9 +0,0 @@
MIT License
Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

16
package-lock.json generated
View File

@ -15,6 +15,7 @@
"@vue/compiler-sfc": "^3.0.0", "@vue/compiler-sfc": "^3.0.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"bootstrap": "^5.0.1", "bootstrap": "^5.0.1",
"bootstrap-icons": "^1.5.0",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"eslint": "^6.7.2", "eslint": "^6.7.2",
"eslint-plugin-vue": "^7.0.0", "eslint-plugin-vue": "^7.0.0",
@ -2854,6 +2855,15 @@
"@popperjs/core": "^2.10.1" "@popperjs/core": "^2.10.1"
} }
}, },
"node_modules/bootstrap-icons": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.5.0.tgz",
"integrity": "sha512-44feMc7DE1Ccpsas/1wioN8ewFJNquvi5FewA06wLnqct7CwMdGDVy41ieHaacogzDqLfG8nADIvMNp9e4bfbA==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npm.taobao.org/brace-expansion/download/brace-expansion-1.1.11.tgz?cache=0&sync_timestamp=1614010713935&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbrace-expansion%2Fdownload%2Fbrace-expansion-1.1.11.tgz", "resolved": "https://registry.npm.taobao.org/brace-expansion/download/brace-expansion-1.1.11.tgz?cache=0&sync_timestamp=1614010713935&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbrace-expansion%2Fdownload%2Fbrace-expansion-1.1.11.tgz",
@ -17155,6 +17165,12 @@
"dev": true, "dev": true,
"requires": {} "requires": {}
}, },
"bootstrap-icons": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.5.0.tgz",
"integrity": "sha512-44feMc7DE1Ccpsas/1wioN8ewFJNquvi5FewA06wLnqct7CwMdGDVy41ieHaacogzDqLfG8nADIvMNp9e4bfbA==",
"dev": true
},
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npm.taobao.org/brace-expansion/download/brace-expansion-1.1.11.tgz?cache=0&sync_timestamp=1614010713935&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbrace-expansion%2Fdownload%2Fbrace-expansion-1.1.11.tgz", "resolved": "https://registry.npm.taobao.org/brace-expansion/download/brace-expansion-1.1.11.tgz?cache=0&sync_timestamp=1614010713935&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbrace-expansion%2Fdownload%2Fbrace-expansion-1.1.11.tgz",

View File

@ -14,6 +14,7 @@
"@vue/compiler-sfc": "^3.0.0", "@vue/compiler-sfc": "^3.0.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"bootstrap": "^5.0.1", "bootstrap": "^5.0.1",
"bootstrap-icons": "^1.5.0",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"eslint": "^6.7.2", "eslint": "^6.7.2",
"eslint-plugin-vue": "^7.0.0", "eslint-plugin-vue": "^7.0.0",

View File

@ -1 +0,0 @@
SPDX-License-Identifier: MIT

View File

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil" viewBox="0 0 16 16">
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
</svg>

Before

Width:  |  Height:  |  Size: 548 B

View File

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-lg" viewBox="0 0 16 16">
<path d="M8 0a1 1 0 0 1 1 1v6h6a1 1 0 1 1 0 2H9v6a1 1 0 1 1-2 0V9H1a1 1 0 0 1 0-2h6V1a1 1 0 0 1 1-1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 238 B

View File

@ -1 +0,0 @@
SPDX-License-Identifier: MIT

View File

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-search" viewBox="0 0 16 16">
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
</svg>

Before

Width:  |  Height:  |  Size: 331 B

View File

@ -1 +0,0 @@
SPDX-License-Identifier: MIT

View File

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
</svg>

Before

Width:  |  Height:  |  Size: 573 B

View File

@ -1 +0,0 @@
SPDX-License-Identifier: MIT

View File

@ -12,8 +12,8 @@ SPDX-License-Identifier: AGPL-3.0-or-later
<Footer /> <Footer />
</template> </template>
<script> <script>
import Footer from '@/components/Footer.vue' import Footer from '@/components/Footer'
import Navbar from '@/components/Navbar.vue' import Navbar from '@/components/Navbar'
export default { export default {
name: 'App', name: 'App',

View File

@ -7,6 +7,13 @@
@import "variables"; @import "variables";
@import "bootstrap/scss/bootstrap"; @import "bootstrap/scss/bootstrap";
.bg-wtf {
background-image: url(../assets/img/wtf-header-bg.jpg);
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
}
.btn-primary { .btn-primary {
color: #fff !important; color: #fff !important;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

View File

@ -14,3 +14,5 @@ $body-bg: $gray-100;
$link-decoration: none; $link-decoration: none;
$link-hover-decoration: underline; $link-hover-decoration: underline;
$spinner-animation-speed: 1s;

View File

@ -27,10 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
aria-label="Hinzufügen" aria-label="Hinzufügen"
@click="addResult()" @click="addResult()"
> >
<img <i clas="bi-plus-lg"></i>
src="/img/bootstrap-icons-1.5.0/plus-lg.svg"
alt="Hinzufügen Icon"
/>
Hinzufügen Hinzufügen
</button> </button>
</div> </div>

37
src/components/Avatar.vue Normal file
View File

@ -0,0 +1,37 @@
<!--
SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<div class="avatar bg-dark">
{{ avatarLetters }}
</div>
</template>
<script>
export default {
props: {
name: String
},
computed: {
avatarLetters() {
return this.name.substr(0, 2)
}
}
}
</script>
<style scoped>
.avatar {
align-items: center;
border-radius: .25rem;
color: white;
display: flex;
font-weight: bold;
height: 2.5rem;
justify-content: center;
width: 2.5rem;
}
</style>

View File

@ -26,59 +26,44 @@ SPDX-License-Identifier: AGPL-3.0-or-later
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div <div
class="collapse navbar-collapse" class="collapse navbar-collapse d-lg-flex"
:class="{ show: showMobileNav }" :class="{ show: showMobileNav }"
id="navbarSupportedContent" id="navbarSupportedContent"
> >
<ul class="navbar-nav me-auto mb-2 mb-lg-0"> <ul class="navbar-nav mb-2 mb-lg-0 me-auto">
<li class="nav-item"> <li class="nav-item">
<router-link <router-link
class="nav-link" class="nav-link"
:to="{ path: `/s/search` }" :to="{ path: `/s/search` }"
active-class="active" active-class="active"
>Suche</router-link >Suche</router-link
> >
</li> </li>
<li class="nav-item"> <li class="nav-item">
<router-link <router-link
class="nav-link" class="nav-link"
:to="{ path: `/s/profile/${currentUserId}` }" :to="{ path: `/s/profile/${currentUserId}` }"
active-class="active" active-class="active"
>Mein Profil</router-link >Mein Profil</router-link
> >
</li> </li>
<li class="nav-item"> <li class="nav-item">
<router-link <router-link
class="nav-link" class="nav-link"
:to="{ path: `/s/profile-edit` }" :to="{ path: `/s/profile-edit` }"
active-class="active" active-class="active"
>Bearbeiten</router-link >Bearbeiten</router-link
> >
</li> </li>
</ul>
<ul class="navbar-nav">
<li class="nav-item"> <li class="nav-item">
<button class="btn btn-outline-danger" @click="logout()"> <button class="btn btn-danger w-100" @click="logout()">
<i class="bi bi-box-arrow-right"></i>
Logout Logout
</button> </button>
</li> </li>
</ul> </ul>
<form class="d-flex" @submit.prevent="searchRedirect()">
<input
class="form-control me-2"
v-model="searchText"
type="search"
placeholder="Profile durchsuchen"
aria-label="Search"
/>
<button
class="btn btn-outline-primary"
type="submit"
>
<img
src="/img/bootstrap-icons-1.5.0/search.svg"
alt="Suche Icon"
/>
</button>
</form>
</div> </div>
</div> </div>
</nav> </nav>
@ -95,7 +80,6 @@ export default {
mixins: [RequestMixin], mixins: [RequestMixin],
data() { data() {
return { return {
searchText: '',
showMobileNav: false showMobileNav: false
} }
}, },
@ -110,9 +94,6 @@ export default {
this.$store.dispatch('clear') this.$store.dispatch('clear')
this.$router.push({ path: '/' }); this.$router.push({ path: '/' });
}, },
searchRedirect() {
this.$router.push({ path: `/s/search?text`, query: { query: this.searchText } } );
},
} }
} }

View File

@ -82,10 +82,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
aria-label="Löschen" aria-label="Löschen"
@click="$emit('removeValue', value[type].name)" @click="$emit('removeValue', value[type].name)"
> >
<img <i class="bi-trash"></i>
src="/img/bootstrap-icons-1.5.0/trash.svg"
alt="Löschen Icon"
/>
</button> </button>
</div> </div>
</div> </div>

View File

@ -0,0 +1,54 @@
<!--
SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<router-link
class="text-decoration-none d-flex"
:to="{ path: `/s/profile/${profile.user_id}` }"
>
<div class="card w-100">
<div class="card-body d-flex">
<div class="d-flex align-items-center justify-content-center me-3">
<Avatar :name="profile.nickname"/>
</div>
<div class="text-body">
<h5 class="card-title mb-1 lh-1">
{{ profile.nickname}}
<span v-if="profile.pronouns"> ({{ profile.pronouns }})</span>
</h5>
<small
class="card-text lh-1 text-dark"
v-if="profile.skills && profile.skills.length > 0"
>
Top-Fähigkeiten: {{ topSkills }}
</small>
</div>
</div>
</div>
</router-link>
</template>
<script>
import Avatar from '@/components/Avatar'
export default {
components: {
Avatar,
},
props: {
profile: Object
},
computed: {
topSkills() {
return this.profile.skills.slice(0, 5).map(s => s.skill.name).join(', ')
}
}
}
</script>
<style scoped>
.card:hover {
background-color: #f8f9fa;
}
</style>

View File

@ -0,0 +1,9 @@
<!--
SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<div class="spinner-grow" role="status"></div>
</template>

View File

@ -7,6 +7,7 @@ import App from './App.vue'
import router from './router' import router from './router'
import store from '@/store' import store from '@/store'
import 'bootstrap-icons/font/bootstrap-icons.css'
import './assets/global.scss' import './assets/global.scss'
const app = createApp(App) const app = createApp(App)

View File

@ -4,12 +4,17 @@
import { createStore } from 'vuex' import { createStore } from 'vuex'
import search from './search'
const localStorageKeys = { const localStorageKeys = {
currentUserId: 'ki_current_user_id', currentUserId: 'ki_current_user_id',
token: 'ki_token', token: 'ki_token',
} }
export default createStore({ export default createStore({
modules: {
search,
},
state() { state() {
return { return {
currentUserId: JSON.parse(localStorage.getItem(localStorageKeys.currentUserId)), currentUserId: JSON.parse(localStorage.getItem(localStorageKeys.currentUserId)),

100
src/store/search.js Normal file
View File

@ -0,0 +1,100 @@
// SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
export default {
namespaced: true,
state() {
return {
searching: false,
showSpinner: false,
searched: false,
profiles: [],
error: false,
errorMessage: '',
query: {
search: ''
}
}
},
mutations: {
setSearching(state, searching) {
state.searching = searching
},
showSpinner(state) {
state.showSpinner = true
},
hideSpinner(state) {
state.showSpinner = false
},
clearProfiles(state) {
state.profiles = []
},
setProfiles(state, profiles) {
state.profiles = profiles
},
setError(state, error) {
state.error = error
},
setErrorMessage(state, errorMessage) {
state.errorMessage = errorMessage
},
setQuerySearch(state, search) {
state.query.search = search
}
},
actions: {
async search({state, commit, rootState}) {
if (state.searching) {
return
}
commit('setSearching', true)
const timeoutId = setTimeout(() => {
commit('showSpinner')
commit('clearProfiles')
}, 100)
commit('setError', false)
commit('setErrorMessage', '')
const url = new URL(`${window.ki.apiUrl}/users/profiles`)
if (state.query.search) {
url.searchParams.append('search', state.query.search)
}
const headers = {
Authorization: `Bearer ${rootState.token}`,
}
let response
try {
response = await fetch(url, {headers})
} catch {
commit('setError', true)
commit('clearProfiles')
commit('setSearching', false)
commit('hideSpinner')
return
}
clearTimeout(timeoutId)
if (!response.ok) {
commit('setError', true)
commit('clearProfiles')
commit('setSearching', false)
commit('hideSpinner')
return
}
const responseData = await response.json()
commit('setProfiles', responseData.profiles)
commit('setSearching', false)
commit('hideSpinner')
}
}
}

View File

@ -5,46 +5,51 @@ SPDX-License-Identifier: AGPL-3.0-or-later
--> -->
<template> <template>
<div class="container pt-5"> <div class="bg-wtf">
<div class="text-center mb-5"> <div class="container pt-5">
<img class="wtf-logo wtf-logo--index" src="@/assets/img/wtf_logo.svg"> <div class="text-center mb-5">
<h1>Kompetenzinventar</h1> <img class="wtf-logo wtf-logo--index" src="@/assets/img/wtf_logo.svg">
<h1 class="text-white">Kompetenzinventar</h1>
</div>
<form @submit.prevent="submitLogin()" class="card bg-white login-form">
<div class="card-body">
<div class="mb-3">
<label for="exampleInputusername1" class="form-label" >
WTF-Benutzername:
</label>
<input
type="username"
class="form-control"
id="exampleInputusername1"
v-model="username"
required
/>
</div>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label">Passwort:</label>
<input
type="password"
class="form-control"
id="exampleInputPassword1"
v-model="password"
required
/>
</div>
<button type="submit" class="btn btn-primary">Login</button>
<a class="btn btn-link" href="https://resetpw.wtf-eg.de/">Passwort vergessen?</a>
<div
class="alert alert-danger mt-3 mb-0"
role="alert"
v-if="showErrorMessage"
>
Dein Benutzername oder Passwort ist falsch.<br>Versuche es noch einmal.
</div>
</div>
</form>
</div> </div>
<form @submit.prevent="submitLogin()" class="bg-white p-3 login-form">
<div class="mb-3">
<label for="exampleInputusername1" class="form-label" >
WTF-Benutzername:
</label>
<input
type="username"
class="form-control"
id="exampleInputusername1"
v-model="username"
required
/>
</div>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label">Passwort:</label>
<input
type="password"
class="form-control"
id="exampleInputPassword1"
v-model="password"
required
/>
</div>
<button type="submit" class="btn btn-primary">Login</button>
<a class="btn btn-link" href="https://resetpw.wtf-eg.de/">Passwort vergessen?</a>
<div
class="alert alert-danger mt-3 mb-0"
role="alert"
v-if="showErrorMessage"
>
Dein Benutzername oder Passwort ist falsch.<br>Versuche es noch einmal.
</div>
</form>
</div> </div>
</template> </template>
<script> <script>
import RequestMixin from "@/mixins/request.mixin" import RequestMixin from "@/mixins/request.mixin"
@ -62,6 +67,7 @@ export default {
</script> </script>
<style scoped> <style scoped>
.container { .container {
min-height: 100vh; min-height: 100vh;
} }

View File

@ -5,88 +5,129 @@ SPDX-License-Identifier: AGPL-3.0-or-later
--> -->
<template> <template>
<div class="container"> <div class="content">
<h1>Suche</h1> <div class="bg-wtf text-white pt-3 pb-4">
<form @submit.prevent="submitSearch()"> <div class="container">
<div class="row"> <div class="fs-3 text-center lh-1 mb-3">Finde WTF Member</div>
<div class="col"> <div class="card mx-auto bg-white">
<input <div class="card-body">
type="text" <form @submit.prevent="handleSubmit">
class="form-control" <fieldset class="d-flex" :disabled="searching">
id="searchText" <div class="flex-grow-1 me-3">
v-model="searchText" <input
/> type="text"
</div> class="form-control"
<div class="col"> id="searchText"
<button type="submit" class="btn btn-primary mb-4"> v-model="searchText"
Suche starten placeholder="Nick, Fähigkeit, Sprache"
</button> ref="searchTextInput"
/>
</div>
<div class="">
<button type="submit" class="btn btn-primary">
<i class="bi-search"></i>
<span class="d-none d-md-inline"> Suchen</span>
</button>
</div>
</fieldset>
</form>
</div>
</div> </div>
</div> </div>
</form>
<div
class="alert alert-danger mb-4 mt-4"
role="alert"
v-if="showErrorMessage"
>
Bei der Suche ist ein Fehler aufgetreten
</div> </div>
<div v-if="searchTotal == 0"> <div class="container pt-4 pb-3">
Es wurde kein Suchergebnis gefunden. <div class="text-center" v-if="showSpinner">
<p v-if="searchText !== ''">Probiere eine andere Suche.</p> <Spinner />
</div> </div>
<div v-else> <div
<div class="row"> class="fs-2 text-danger text-center"
<div role="alert"
class="col-4 p-2" v-if="error"
v-for="result in searchResults" >
:key="result.user_id" <div class="fs-1 mb-3">Kernel panic :/</div>
> Bei der Suche ist ein Fehler aufgetreten.
<router-link </div>
class="text-decoration-none" <div v-else-if="showNoResults" class="fs-2 text-black-50 text-center">
:to="{ path: `/s/profile/${result.user_id}` }" <div class="fs-1 mb-3">nullptr :/</div>
> Es wurde kein Suchergebnis gefunden.
<div class="card search-card"> <p v-if="searchText !== ''">Probiere eine andere Suche.</p>
<div class="card-body"> </div>
<h5 class="card-title"> <div v-else-if="showResults">
{{ result.nickname}} <SearchResult
<span v-if="result.pronouns"> ({{ result.pronouns }})</span> v-for="profile in profiles"
</h5> :key="profile.user_id"
<p class="mb-3"
class="card-text" :profile="profile"
v-if="result.skills && result.skills.length > 0" />
>
Fähigkeiten:
<span v-for="(skill, index) in result.skills" :key="index"
>
{{ skill.skill.name}}<span v-if="index != result.skills.length - 1">, </span>
</span>
</p>
</div>
</div>
</router-link>
</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import RequestMixin from "@/mixins/request.mixin"; import { mapState } from 'vuex'
import SearchResult from '@/components/SearchResult'
import Spinner from '@/components/Spinner'
export default { export default {
name: "Search", name: 'Search',
mixins: [RequestMixin], components: {
data() { SearchResult,
return { Spinner,
showErrorMessage: false, },
searchText: "", computed: {
searchResults: null, ...mapState({
searchTotal: 0, searching: state => state.search.searching,
}; profiles: state => state.search.profiles,
error: state => state.search.error,
showSpinner: state => state.search.showSpinner,
}),
searchText: {
get() {
return this.$store.state.search.query.search
},
set(text) {
this.$store.commit('search/setQuerySearch', text)
}
},
showNoResults() {
return !this.searching && (!this.profiles || this.profiles.length === 0)
},
showResults() {
return !this.error && this.profiles && this.profiles.length > 0
}
},
watch: {
searching(value) {
if (value) {
if (this.$refs.searchTextInput) {
this.$refs.searchTextInput.focus()
}
}
}
},
methods: {
handleSubmit() {
this.$store.dispatch('search/search')
}
}, },
created() { created() {
if (this.$route.query.query) this.searchText = this.$route.query.query; if (this.$route.query.query !== undefined) {
this.submitSearch(); this.searchText = this.$route.query.query
}, this.$store.commit('search/clearProfiles')
}
this.$store.dispatch('search/search')
}
}; };
</script> </script>
<style scoped>
.container {
max-width: 768px;
}
.content {
min-height: calc(100vh - 60px);
}
</style>