Compare commits

..

2 Commits

Author SHA1 Message Date
579dfc10ff Große Anpassung 2025-08-29 22:07:18 +02:00
c6fd026ba2 Kleine Anpassungen 2025-08-29 22:06:40 +02:00
8 changed files with 883 additions and 785 deletions

1
components.d.ts vendored
View File

@ -31,7 +31,6 @@ declare module 'vue' {
BookShops: typeof import('./src/components/bookdetail/BookShops.vue')['default']
BookStats: typeof import('./src/components/bookdetail/BookStats.vue')['default']
Buchkarten: typeof import('./src/components/Buchkarten.vue')['default']
Buecherdatenbank: typeof import('./src/pages/buecherdatenbank.vue')['default']
BuyNow: typeof import('./src/@core/components/BuyNow.vue')['default']
CardAddEditDialog: typeof import('./src/components/dialogs/CardAddEditDialog.vue')['default']
CardStatisticsHorizontal: typeof import('./src/@core/components/cards/CardStatisticsHorizontal.vue')['default']

223
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -67,9 +67,10 @@
</div>
<!-- Reset Button -->
<div v-if="!isStatic" class="reset-container">
<!-- <div v-if="!isStatic" class="reset-container">
<button @click="resetSliders">Fieberkurve zurücksetzen</button>
</div>
</div>-->
</div>
</template>
@ -179,6 +180,10 @@ onMounted(() => {
align-items: center;
}
.item1{
width: 105px;
}
.item1,
.item3 {
background: transparent;

View File

@ -3,6 +3,7 @@
<VCardTitle>Deine Aktionen</VCardTitle>
<VCardText>
<div class="d-flex flex-column gap-2">
<VBtn :color="liked ? 'primary' : 'grey'" @click="toggleLike" variant="outlined">
<VIcon
start

View File

@ -1,82 +1,105 @@
// VERSION 1:
/*import axios from 'axios'
// BookService.js
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'
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 {
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 {
const response = await axios.get(`${baseUrl}${isbn}`, {
timeout: 5000, // Adjust timeout as needed
})
const { data } = await axios.post(
`${registrationBase}`,
payload, // <-- reines JSON, kein Wrapper
{
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
timeout: 8000,
},
)
return response.data
} catch (error) {
console.error('Fehler beim Abrufen des Buches:', error)
console.error('Antwort vom Server:', error.response)
return data
} catch (errJson) {
// Heuristiken, wann wir auf die 'book=' Variante zurückfallen sollten
const msg = (errJson?.response?.data && JSON.stringify(errJson.response.data)) || `${errJson}`
// Detailliertere Fehlerbehandlung basierend auf dem Fehlertyp
if (error.response && error.response.status === 404) {
console.error('Buch nicht gefunden')
} else if (error.code === 'ECONNABORTED') {
console.error('Verbindungsabbruch')
} else if (error.response && error.response.status === 500) {
console.error('Interner Serverfehler')
} else {
console.error('Unbekannter Fehler')
const shouldRetryAsForm =
/Required request parameter 'book'|MissingServletRequestParameter/i.test(msg) ||
/Failed to read request|HTTP message not readable/i.test(msg) ||
// manche Backends antworten schlicht 500, wenn sie query-param erwarten
(/Failed to convert value of type 'java\.lang\.String' to required type 'de\.skoutz\.backend\.book\.Book'/i.test(msg))
if (!shouldRetryAsForm) {
throw errJson
}
// Zeige dem Benutzer eine benutzerfreundliche Fehlermeldung an
throw new Error('Ein Fehler ist beim Abrufen des Buchs aufgetreten.')
// 2) Fallback: form-url-encoded mit 'book=<json>'
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
}
}
},
}
// ********************************
// 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")
async isIsbnRegistered(isbn) {
const { data } = await axios.get(`${registrationBase}/isIsbnRegistered/${encodeURIComponent(isbn)}`, {
timeout: 5000, headers: { Accept: 'application/json' },
})
return data === true || data === 'true'
},
}*/
}

View File

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