Files
yourpart3/frontend/src/views/social/SearchView.vue

275 lines
7.6 KiB
Vue

<template>
<div class="search-view">
<section class="search-hero surface-card">
<div>
<span class="search-kicker">Community-Suche</span>
<h2>{{ $t('socialnetwork.usersearch.title') }}</h2>
<p>Mit Namen, Alter und Geschlecht gezielt passende Kontakte in der Community finden.</p>
</div>
</section>
<section class="search-form surface-card">
<form @submit.prevent="performSearch">
<div class="form-grid">
<div class="form-group">
<label for="username">{{ $t('socialnetwork.usersearch.username') }}</label>
<input type="text" id="username" v-model="searchCriteria.username"
:placeholder="$t('socialnetwork.usersearch.username')" />
</div>
<div class="form-group form-group--age">
<label for="ageFrom">{{ $t('socialnetwork.usersearch.age_from') }}</label>
<div class="age-range">
<input type="number" id="ageFrom" v-model="searchCriteria.ageFrom" :min="14" :max="150"
:placeholder="$t('socialnetwork.usersearch.age_from')" class="age-input" />
<span class="age-separator">bis</span>
<input type="number" id="ageTo" v-model="searchCriteria.ageTo" :min="14" :max="150"
:placeholder="$t('socialnetwork.usersearch.age_to')" class="age-input" />
</div>
</div>
<div class="form-group">
<label for="gender">{{ $t('socialnetwork.usersearch.gender') }}</label>
<multiselect v-model="searchCriteria.gender" :options="genderOptions" :multiple="true"
:close-on-select="false" :placeholder="$t('socialnetwork.usersearch.gender')" label="name"
track-by="name" />
</div>
</div>
<div class="form-actions">
<button type="submit" class="search-button">{{ $t('socialnetwork.usersearch.search_button') }}</button>
</div>
</form>
</section>
<section class="search-results surface-card" v-if="searchResults.length">
<div class="results-header">
<h3>{{ $t('socialnetwork.usersearch.results_title') }}</h3>
<span class="results-count">{{ searchResults.length }} Treffer</span>
</div>
<div class="result-cards">
<article v-for="result in searchResults" :key="result.id" class="result-card">
<div class="result-card__main">
<span @click.prevent="openUserProfile(result.id)" :class="'clickable g-' + result.gender">{{ result.username }}</span>
<div class="result-card__meta">
<span>{{ $t("socialnetwork.usersearch.result.gender") }}: {{ result.gender }}</span>
<span>{{ $t("socialnetwork.usersearch.result.age") }}: {{ result.age }}</span>
</div>
</div>
<button type="button" class="button-secondary" @click="openUserProfile(result.id)">
Profil öffnen
</button>
</article>
</div>
</section>
<div v-else class="no-results surface-card">
{{ $t('socialnetwork.usersearch.no_results') }}
</div>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect';
import 'vue-multiselect/dist/vue-multiselect.min.css';
import apiClient from '@/utils/axios.js';
export default {
components: {
Multiselect,
},
data() {
return {
searchCriteria: {
username: '',
ageFrom: 14,
ageTo: 150,
gender: []
},
genderOptions: [],
searchResults: []
};
},
async mounted() {
await this.loadGenderOptions();
},
methods: {
async loadGenderOptions() {
try {
const response = await apiClient.post('/api/settings/getparamvalues', {
type: 'gender'
});
this.genderOptions = response.data.map(g => ({ name: g.name, value: g.value }));
} catch (error) {
console.error('Fehler beim Laden der Geschlechtsoptionen:', error);
}
},
async performSearch() {
const searchCriteria = {
username: this.searchCriteria.username,
ageFrom: this.searchCriteria.ageFrom,
ageTo: this.searchCriteria.ageTo,
gender: this.searchCriteria.gender.map(g => g.value)
};
try {
const response = await apiClient.post('/api/socialnetwork/usersearch', searchCriteria);
this.searchResults = response.data;
} catch (error) {
console.error('Fehler bei der Suche:', error);
}
},
openUserProfile(id) {
this.$root.$refs.userProfileDialog.userId = id;
this.$root.$refs.userProfileDialog.open();
}
}
};
</script>
<style scoped>
.search-view {
max-width: var(--content-max-width);
margin: 0 auto;
padding-bottom: 24px;
}
.search-hero,
.search-form,
.search-results,
.no-results {
padding: 22px;
margin-bottom: 16px;
}
.search-kicker {
display: inline-block;
margin-bottom: 10px;
padding: 4px 10px;
border-radius: 999px;
background: rgba(120, 195, 138, 0.14);
color: #42634e;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.search-hero p {
margin: 0;
color: var(--color-text-secondary);
}
.form-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 8px;
}
label {
font-weight: 700;
color: var(--color-text-secondary);
}
.age-range {
display: flex;
align-items: center;
gap: 8px;
}
.age-input {
width: 100%;
}
.age-separator {
color: var(--color-text-muted);
font-size: 0.88rem;
white-space: nowrap;
}
.form-actions {
margin-top: 14px;
}
.results-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.results-count {
color: var(--color-text-muted);
font-size: 0.82rem;
font-weight: 700;
}
.result-cards {
display: grid;
gap: 12px;
}
.result-card {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 16px 18px;
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
background: rgba(255, 255, 255, 0.66);
}
.result-card__main {
display: flex;
flex-direction: column;
gap: 6px;
}
.result-card__meta {
display: flex;
flex-wrap: wrap;
gap: 10px 16px;
color: var(--color-text-secondary);
font-size: 0.92rem;
}
.clickable {
cursor: pointer;
font-weight: 700;
}
.no-results {
text-align: center;
color: var(--color-text-secondary);
}
.g-male {
color: #3377ff;
}
.g-female {
color: #ff3377;
}
@media (max-width: 960px) {
.form-grid {
grid-template-columns: 1fr;
}
.age-range {
flex-wrap: wrap;
}
.result-card {
flex-direction: column;
align-items: flex-start;
}
}
</style>