Große Anpassung

This commit is contained in:
Michi Tomaschko 2025-08-29 22:07:18 +02:00
parent c6fd026ba2
commit 579dfc10ff
4 changed files with 705 additions and 729 deletions

View File

@ -1,82 +1,105 @@
// VERSION 1: // BookService.js
/*import axios from 'axios'
const baseUrl = 'http://localhost:8001/api/v1/book/registration/search/isbn/' // Passe die URL an
export default {
getBookByIsbn(isbn) {
return axios.get(`${baseUrl}${isbn}`, {
headers: {
'accept': 'application/json',
},
})
.catch(error => {
// Detailliertere Fehlerbehandlung
console.error('Fehler beim Abrufen des Buches:', error)
// Zeige dem Benutzer eine informative Fehlermeldung an
})
},
}*/
// ********************************
// VERSION 2:
import axios from 'axios' import axios from 'axios'
const baseUrl = 'http://localhost:8001/api/v1/book/registration/search/isbn/' // const baseUrl = 'http://localhost:8001/api/v1/book/suggestion'
// const baseUrl = 'http://h2983688.stratoserver.net:8001/api/v1/book/suggestion'
// const baseUrl= 'https://skoutz-backend.it-tomaschko.de/api/v1/book/suggestion'
const suggestionBase = 'http://localhost:8001/api/v1/book/suggestion'
const registrationBase = 'http://localhost:8001/api/v1/book/registration'
export default { export default {
async getBookByIsbn(isbn) { async getBookByIsbn(isbn) {
const { data } = await axios.get(`${suggestionBase}/search/isbn/${encodeURIComponent(isbn)}`, {
timeout: 5000, headers: { Accept: 'application/json' },
})
return data
},
async getBookByTitle(title) {
const { data } = await axios.get(`${suggestionBase}/search/title`, {
params: { title }, timeout: 5000, headers: { Accept: 'application/json' },
})
return data // Array von Vorschlägen
},
async registerBook(payload) {
// 1) Versuch: JSON-Body (das ist in modernen Spring-Stacks Standard)
try { try {
const response = await axios.get(`${baseUrl}${isbn}`, { const { data } = await axios.post(
timeout: 5000, // Adjust timeout as needed `${registrationBase}`,
}) payload, // <-- reines JSON, kein Wrapper
{
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
timeout: 8000,
},
)
return response.data return data
} catch (error) { } catch (errJson) {
console.error('Fehler beim Abrufen des Buches:', error) // Heuristiken, wann wir auf die 'book=' Variante zurückfallen sollten
console.error('Antwort vom Server:', error.response) const msg = (errJson?.response?.data && JSON.stringify(errJson.response.data)) || `${errJson}`
// Detailliertere Fehlerbehandlung basierend auf dem Fehlertyp const shouldRetryAsForm =
if (error.response && error.response.status === 404) { /Required request parameter 'book'|MissingServletRequestParameter/i.test(msg) ||
console.error('Buch nicht gefunden') /Failed to read request|HTTP message not readable/i.test(msg) ||
} else if (error.code === 'ECONNABORTED') {
console.error('Verbindungsabbruch') // manche Backends antworten schlicht 500, wenn sie query-param erwarten
} else if (error.response && error.response.status === 500) { (/Failed to convert value of type 'java\.lang\.String' to required type 'de\.skoutz\.backend\.book\.Book'/i.test(msg))
console.error('Interner Serverfehler')
} else { if (!shouldRetryAsForm) {
console.error('Unbekannter Fehler') throw errJson
} }
// Zeige dem Benutzer eine benutzerfreundliche Fehlermeldung an // 2) Fallback: form-url-encoded mit 'book=<json>'
throw new Error('Ein Fehler ist beim Abrufen des Buchs aufgetreten.') try {
const body = new URLSearchParams()
body.set('book', JSON.stringify(payload))
const { data } = await axios.post(
`${registrationBase}`,
body,
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json',
},
timeout: 8000,
},
)
return data
} catch (errForm) {
// schöner Fehlertext
const status = errForm?.response?.status
const backendMsg = errForm?.response?.data?.message || errForm?.response?.data?.error
const msg2 =
backendMsg ? `${status ?? ''} ${backendMsg}`.trim() :
status === 400 ? 'Ungültige Daten (400).' :
status === 409 ? 'ISBN bereits registriert (409).' :
status === 500 ? 'Serverfehler (500).' :
'Unbekannter Fehler beim Registrieren.'
const e = new Error(msg2)
e.response = errForm?.response; throw e
}
} }
}, },
}
async isIsbnRegistered(isbn) {
const { data } = await axios.get(`${registrationBase}/isIsbnRegistered/${encodeURIComponent(isbn)}`, {
timeout: 5000, headers: { Accept: 'application/json' },
})
// ******************************** return data === true || data === 'true'
// VERSION 3:
/*const axios = require('axios')
export default {
getBookByIsbn(isbn) {
// Make a request for a user with a given ID
console.log(isbn)
axios.get('http://localhost:8001/api/v1/book/registration/search/isbn/9783898798822')
.then(function (response) {
// handle success
console.log("hi", response)
})
.catch(function (error) {
// handle error
console.log(error)
console.log("hi")
})
.finally(function () {
// always executed
console.log("hi")
})
}, },
}*/ }

View File

@ -4,7 +4,9 @@
<VRow> <VRow>
<!-- Linke Spalte --> <!-- Linke Spalte -->
<VCol cols="12" md="3"> <VCol cols="12" md="3">
<div class="mb-4" style="aspect-ratio: 2/3; overflow: hidden;"> <div
class="mb-4 cover-wrapper"
>
<VImg :src="book.image" height="100%" width="100%" cover class="elevation-1" /> <VImg :src="book.image" height="100%" width="100%" cover class="elevation-1" />
</div> </div>
@ -164,3 +166,17 @@ const genreIcon = genre => {
return icons[genre] || 'tabler-book' return icons[genre] || 'tabler-book'
} }
</script> </script>
<style scoped>
.cover-wrapper {
aspect-ratio: 2 / 3;
overflow: hidden;
//transition: transform 0.3s ease, box-shadow 0.3s ease;
border-radius: 8px;
}
/*.cover-wrapper:hover {
transform: scale(1.02);
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.15);
}*/
</style>

File diff suppressed because it is too large Load Diff

View File

@ -14,10 +14,14 @@
@update:values="updateFieberkurveValues" @update:values="updateFieberkurveValues"
@filter-by-row="filterByRow" @filter-by-row="filterByRow"
/> />
<div class="d-flex justify-start my-2">
<VBtn variant="text" color="primary" @click="fieberkurveRef?.resetSliders()">
<VIcon icon="tabler-refresh" class="mr-1" /> Fieberkurve zurücksetzen
</VBtn>
</div>
<br> <br>
<!-- Genres --> <!-- Genres -->
<div class="genre-section-main"> <div class="genre-section-main">
<div <div
@ -37,14 +41,14 @@
</VTooltip> </VTooltip>
</div> </div>
</div> </div>
<div class="d-flex justify-start my-2">
<!-- Button zum Zurücksetzen der Genres --> <VBtn variant="text" color="primary" @click="clearGenres">
<VBtn class="my-3 mx-auto d-block" color="primary" @click="clearGenres"> <VIcon icon="tabler-refresh" class="mr-1" /> Genres zurücksetzen
Alle Genres Zurücksetzen </VBtn>
</VBtn> </div>
<!-- Text Suchfeld --> <!-- Text Suchfeld -->
<div style="padding: 0 5rem;"> <div style="padding: 0 15rem;">
<AppTextField <AppTextField
v-model="message" v-model="message"
clearable clearable
@ -61,26 +65,22 @@
</template> </template>
</AppTextField> </AppTextField>
</div> </div>
<div class="d-flex justify-start my-2">
<VBtn variant="text" color="error" @click="resetAllSettings">
<VIcon icon="tabler-trash" class="mr-1" /> Alles zurücksetzen
</VBtn>
</div>
<!-- Button zum Zurücksetzen der Einstellungen --> <br>
<VBtn
class="my-3 mx-auto d-block"
:color="resetFeedback ? 'success' : 'error'"
@click="resetAllSettings"
>
{{ resetFeedback ? 'Zurückgesetzt!' : 'Alle Einstellungen zurücksetzen' }}
</VBtn>
<br />
</VCard> </VCard>
</VCol> </VCol>
</VRow> </VRow>
<!-- Trefferanzahl --> <!-- Trefferanzahl -->
<!-- <VDivider class="my-2" />-->
<VCardTitle class="text-h5 px-6 pt-4 pb-2"> <VCardTitle class="text-h5 px-6 pt-4 pb-2">
{{ filteredBooks.length }} {{ filteredBooks.length === 1 ? 'Buch' : 'Bücher' }} gefunden {{ filteredBooks.length }} {{ filteredBooks.length === 1 ? 'Buch' : 'Bücher' }} gefunden
</VCardTitle> </VCardTitle>
<!-- <VDivider class="my-2" />-->
<!-- Gefundene Bücher --> <!-- Gefundene Bücher -->
<VRow> <VRow>
<Buchkarten :books="filteredBooks" /> <Buchkarten :books="filteredBooks" />
@ -88,90 +88,64 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from "vue"; import { ref, computed } from "vue"
import Fieberkurve from "@/components/Fieberkurve.vue"; import Fieberkurve from "@/components/Fieberkurve.vue"
import Buchkarten from '@/components/Buchkarten.vue'; import Buchkarten from '@/components/Buchkarten.vue'
import { books } from "@/components/buecherdatenbank"; import { books } from "@/components/buecherdatenbank"
import { genres } from "@/components/genredatenbank"; import { genres } from "@/components/genredatenbank"
const message = ref(""); const message = ref("")
const loading = ref(false); const selectedCheckbox = ref([])
const selectedCheckbox = ref([]); const fieberkurveValues = ref([4, 4, 4, 4, 4])
const fieberkurveValues = ref([4, 4, 4, 4, 4]); const fieberkurveRowFilter = ref([])
const fieberkurveRowFilter = ref([]); const fieberkurveRef = ref(null)
const resultCount = computed(() => filteredBooks.value.length)
const filteredBooks = computed(() => { const filteredBooks = computed(() => {
return books.filter((book) => { return books.filter(book => {
const matchesGenres = const matchesGenres =
selectedCheckbox.value.length === 0 || selectedCheckbox.value.length === 0 ||
selectedCheckbox.value.some((genre) => book.genres.includes(genre)); selectedCheckbox.value.some(genre => book.genres.includes(genre))
const matchesText = const matchesText =
!message.value || !message.value ||
book.title.toLowerCase().includes(message.value.toLowerCase()) || book.title.toLowerCase().includes(message.value.toLowerCase()) ||
book.authors?.some((author) => book.authors?.some(author =>
author.name.toLowerCase().includes(message.value.toLowerCase()) author.name.toLowerCase().includes(message.value.toLowerCase()),
) || ) ||
book.isbn?.includes(message.value); book.isbn?.includes(message.value)
const hasRowFilters = fieberkurveRowFilter.value.length > 0; const hasRowFilters = fieberkurveRowFilter.value.length > 0
const matchesFieberkurve = !hasRowFilters && fieberkurveValues.value.every((value, index) => { const matchesFieberkurve =
const bookValue = book.fieberkurve[index]?.value || 4; !hasRowFilters &&
return value === 4 || value === bookValue; fieberkurveValues.value.every((value, index) => {
}); const bookValue = book.fieberkurve[index]?.value || 4
const matchesFieberkurveRowFilters = hasRowFilters && fieberkurveRowFilter.value.every(({ rowIndex, value }) => { return value === 4 || value === bookValue
return book.fieberkurve[rowIndex]?.value === value; })
});
return matchesGenres && matchesText && (matchesFieberkurve || matchesFieberkurveRowFilters); const matchesFieberkurveRowFilters =
}); hasRowFilters &&
}); fieberkurveRowFilter.value.every(({ rowIndex, value }) => {
return book.fieberkurve[rowIndex]?.value === value
})
const searchBooks = () => { return matchesGenres && matchesText && (matchesFieberkurve || matchesFieberkurveRowFilters)
console.log("Suche gestartet mit Text:", message.value); })
}; })
const toggleGenre = (genre) => {
if (selectedCheckbox.value.includes(genre)) {
selectedCheckbox.value = selectedCheckbox.value.filter((g) => g !== genre);
} else {
selectedCheckbox.value.push(genre);
}
};
const clearGenres = () => {
selectedCheckbox.value = [];
};
const fieberkurveRef = ref(null);
const resetFeedback = ref(false);
const searchBooks = () => { console.log("Suche gestartet mit Text:", message.value) }
const toggleGenre = genre => selectedCheckbox.value.includes(genre) ? selectedCheckbox.value = selectedCheckbox.value.filter(g => g !== genre) : selectedCheckbox.value.push(genre)
const clearGenres = () => { selectedCheckbox.value = [] }
const resetAllSettings = () => { const resetAllSettings = () => {
selectedCheckbox.value = []; clearGenres(); fieberkurveRef.value?.resetSliders(); message.value = ""; fieberkurveRowFilter.value = [];
fieberkurveRef.value?.resetSliders(); }
message.value = ""; const updateFieberkurveValues = values => { fieberkurveValues.value = values }
fieberkurveRowFilter.value = []; const filterByRow = activeFilters => { fieberkurveRowFilter.value = activeFilters }
resetFeedback.value = true;
setTimeout(() => {
resetFeedback.value = false;
}, 1500);
};
const updateFieberkurveValues = (values) => {
fieberkurveValues.value = values;
};
const filterByRow = (activeFilters) => {
fieberkurveRowFilter.value = activeFilters;
console.log("Aktive Fieberkurvenfilter:", activeFilters);
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.genre-section-main { .genre-section-main {
display: flex; display: flex;