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:
170
frontend/src/dialogues/socialnetwork/VocabSearchDialog.vue
Normal file
170
frontend/src/dialogues/socialnetwork/VocabSearchDialog.vue
Normal 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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user