Add search functionality for vocabulary in VocabController and VocabService

- Implemented a new searchVocabs method in VocabService to allow users to search for vocabulary based on learning and mother tongue terms.
- Updated VocabController to include the searchVocabs method wrapped with user authentication.
- Added a new route in vocabRouter for searching vocabulary by language ID.
- Enhanced VocabChapterView and VocabLanguageView components to include a button for opening the search dialog.
- Added translations for search-related terms in both German and English locales, improving user accessibility.
This commit is contained in:
Torsten Schulz (local)
2026-01-05 16:53:38 +01:00
parent dab3391aa2
commit f5e3a9a4a2
8 changed files with 266 additions and 1 deletions

View File

@@ -0,0 +1,170 @@
<template>
<DialogWidget
ref="dialog"
:title="$t('socialnetwork.vocab.search.title')"
:show-close="true"
:buttons="buttons"
:modal="true"
:isTitleTranslated="false"
width="60em"
height="34em"
name="VocabSearchDialog"
display="flex"
@close="close"
>
<div class="layout">
<div class="top">
<div class="row">
<label class="field">
{{ $t('socialnetwork.vocab.search.motherTongue') }}
<input v-model="motherTongue" type="text" @keydown.enter.prevent="runSearch" />
</label>
<label class="field">
{{ learningLabel }}
<input v-model="learning" type="text" @keydown.enter.prevent="runSearch" />
</label>
<button class="btn" :disabled="loading || (!motherTongue.trim() && !learning.trim())" @click="runSearch">
{{ loading ? $t('general.loading') : $t('socialnetwork.vocab.search.search') }}
</button>
</div>
</div>
<div class="body">
<div v-if="error" class="error">{{ error }}</div>
<div v-else-if="results.length === 0">
{{ $t('socialnetwork.vocab.search.noResults') }}
</div>
<table v-else class="tbl">
<thead>
<tr>
<th>{{ $t('socialnetwork.vocab.search.motherTongue') }}</th>
<th>{{ learningLabel }}</th>
<th>{{ $t('socialnetwork.vocab.search.lesson') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="r in results" :key="r.id">
<td>{{ r.motherTongue }}</td>
<td>{{ r.learning }}</td>
<td>{{ r.chapterTitle }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</DialogWidget>
</template>
<script>
import DialogWidget from '@/components/DialogWidget.vue';
import apiClient from '@/utils/axios.js';
export default {
name: 'VocabSearchDialog',
components: { DialogWidget },
data() {
return {
languageId: null,
languageName: '',
motherTongue: '',
learning: '',
loading: false,
results: [],
error: '',
};
},
computed: {
buttons() {
return [{ text: this.$t('message.close'), action: this.close }];
},
learningLabel() {
return this.languageName || this.$t('socialnetwork.vocab.search.learningLanguage');
},
},
methods: {
open({ languageId, languageName = '' } = {}) {
this.languageId = languageId;
this.languageName = languageName || '';
this.motherTongue = '';
this.learning = '';
this.results = [];
this.error = '';
this.loading = false;
this.$refs.dialog.open();
this.$nextTick(() => {});
},
close() {
this.$refs.dialog.close();
},
async runSearch() {
if (!this.languageId) return;
const mt = this.motherTongue.trim();
const l = this.learning.trim();
if (!mt && !l) return;
this.loading = true;
this.error = '';
try {
const res = await apiClient.get(`/api/vocab/languages/${this.languageId}/search`, {
params: {
motherTongue: mt || undefined,
learning: l || undefined,
},
});
this.results = res.data?.results || [];
} catch (e) {
console.error('Search failed:', e);
this.results = [];
this.error = this.$t('socialnetwork.vocab.search.error');
} finally {
this.loading = false;
}
},
},
};
</script>
<style scoped>
.layout {
display: flex;
flex-direction: column;
gap: 10px;
height: 100%;
}
.top .row {
display: flex;
gap: 10px;
align-items: flex-end;
}
.field {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
}
.field input {
padding: 6px;
}
.btn {
padding: 8px 12px;
}
.body {
flex: 1;
overflow: auto;
}
.tbl {
width: 100%;
border-collapse: collapse;
}
.tbl th,
.tbl td {
border: 1px solid #ccc;
padding: 6px;
}
.error {
color: #b00020;
margin-bottom: 8px;
}
</style>