Book form: add a field to write a review
This commit is contained in:
@@ -3,8 +3,7 @@ import { ref, computed } from 'vue'
|
||||
import {
|
||||
getBook,
|
||||
getImagePathOrDefault,
|
||||
putWantReadBook,
|
||||
putRateBook,
|
||||
putUpdateBook,
|
||||
putStartReadDate,
|
||||
putStartReadDateUnset,
|
||||
putEndReadDate,
|
||||
@@ -14,7 +13,7 @@ import {
|
||||
import { useRouter, onBeforeRouteUpdate } from 'vue-router'
|
||||
import { VRating } from 'vuetify/components/VRating'
|
||||
import BookFormIcons from './BookFormIcons.vue'
|
||||
import ReviewModal from './ReviewModal.vue'
|
||||
import ReviewWidget from './ReviewWidget.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const props = defineProps({
|
||||
@@ -37,7 +36,12 @@ function onRatingUpdate(rating) {
|
||||
data.value.read = true
|
||||
data.value.wantread = false
|
||||
}
|
||||
putRateBook(props.id, { rating: data.value.rating })
|
||||
putUpdateBook(props.id, { rating: data.value.rating })
|
||||
}
|
||||
|
||||
function onReviewUpdate(review) {
|
||||
data.value.review = review
|
||||
putUpdateBook(props.id, { review: data.value.review })
|
||||
}
|
||||
|
||||
async function onReadIconClick() {
|
||||
@@ -53,7 +57,7 @@ async function onReadIconClick() {
|
||||
|
||||
function onWantReadIconClick() {
|
||||
data.value.wantread = !data.value.wantread
|
||||
putWantReadBook(props.id, { wantread: data.value.wantread })
|
||||
putUpdateBook(props.id, { wantread: data.value.wantread })
|
||||
}
|
||||
|
||||
async function onStartReadIconClick() {
|
||||
@@ -100,18 +104,6 @@ function goToAuthor() {
|
||||
<figure class="image">
|
||||
<img v-bind:src="imagePathOrDefault" v-bind:alt="data.title" />
|
||||
</figure>
|
||||
<VRating
|
||||
half-increments
|
||||
hover
|
||||
:length="5"
|
||||
size="x-large"
|
||||
density="compact"
|
||||
:model-value="data.rating / 2"
|
||||
@update:modelValue="onRatingUpdate"
|
||||
active-color="bulma-body-color"
|
||||
class="centered mt-2"
|
||||
/>
|
||||
<ReviewModal button-parent-class="centered mt-3" />
|
||||
</div>
|
||||
<div class="column">
|
||||
<h3 class="title">{{ data.title }}</h3>
|
||||
@@ -120,6 +112,7 @@ function goToAuthor() {
|
||||
<div class="my-5" v-if="data.isbn">ISBN: {{ data.isbn }}</div>
|
||||
<div class="my-5" v-if="data.inventaireid">Inventaire ID: {{ data.inventaireid }}</div>
|
||||
<div class="my-5" v-if="data.openlibraryid">OLID: {{ data.openlibraryid }}</div>
|
||||
<ReviewWidget :reviewtext="data.review" :rating="data.rating" @on-review-update="onReviewUpdate" @on-rating-update="onRatingUpdate"/>
|
||||
</div>
|
||||
<div class="column">
|
||||
<BookFormIcons
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
buttonParentClass: String,
|
||||
})
|
||||
|
||||
const open = ref(false)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="buttonParentClass">
|
||||
<button @click="open = true" class="button is-large is-responsive">
|
||||
<span class="icon">
|
||||
<b-icon-pen/>
|
||||
</span>
|
||||
<span>{{$t('bookform.reviewbtn')}}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Teleport to="body">
|
||||
<div v-if="open">
|
||||
<div @click="open = false" class="modal-backdrop"></div>
|
||||
<div class="modal has-background-dark">
|
||||
<h2 class="subtitle">{{$t('bookform.reviewbtn')}}</h2>
|
||||
<textarea class="textarea" :placeholder="$t('bookform.reviewbtn')"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.modal {
|
||||
height: 80%;
|
||||
width: 80%;
|
||||
top: 10%;
|
||||
left: 10%;
|
||||
box-shadow: 2px 2px 20px 1px;
|
||||
overflow-x: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 15px;
|
||||
z-index: 1000;
|
||||
}
|
||||
</style>
|
||||
134
front/src/ReviewWidget.vue
Normal file
134
front/src/ReviewWidget.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { VRating } from 'vuetify/components/VRating'
|
||||
|
||||
const props = defineProps({
|
||||
rating: Number,
|
||||
reviewtext: String,
|
||||
})
|
||||
const isTextareaExpanded = ref(false)
|
||||
const isTextareaTransitionEnabled = ref(true)
|
||||
|
||||
defineEmits('onRatingUpdate', 'onReviewUpdate')
|
||||
|
||||
function computeTextareaClass() {
|
||||
let classAttr = isTextareaExpanded && isTextareaExpanded.value ? 'textarea-expanded' : 'textarea-normal'
|
||||
if (isTextareaTransitionEnabled && isTextareaTransitionEnabled.value) {
|
||||
classAttr += ' transition-height'
|
||||
}
|
||||
return classAttr
|
||||
}
|
||||
|
||||
function onTextAreaFocus() {
|
||||
isTextareaExpanded.value = true
|
||||
setTimeout(() => {
|
||||
isTextareaTransitionEnabled.value = false
|
||||
}, 500)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="maincontainer py-5">
|
||||
<div class="widget-header mb-5 full-width">
|
||||
<div class="widget-title ml-3">
|
||||
<h2>{{$t('review.title')}}</h2>
|
||||
<span class="ml-3">
|
||||
<b-icon-pen/>
|
||||
</span>
|
||||
</div>
|
||||
<VRating
|
||||
half-increments
|
||||
hover
|
||||
:length="5"
|
||||
size="x-large"
|
||||
density="compact"
|
||||
:model-value="rating / 2"
|
||||
@update:modelValue="(r) => $emit('onRatingUpdate', r)"
|
||||
active-color="bulma-body-color"
|
||||
class="widget-rating centered"
|
||||
/>
|
||||
</div>
|
||||
<div class="full-width centered">
|
||||
<textarea
|
||||
:placeholder="$t('review.textplaceholder')"
|
||||
class="widget-textarea mx-4"
|
||||
@change="(e) => $emit('onReviewUpdate', e.target.value)"
|
||||
@focus="onTextAreaFocus"
|
||||
:class="computeTextareaClass()">{{reviewtext}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.maincontainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
border: solid;
|
||||
border-radius: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.widget-header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.widget-title {
|
||||
flex: 2;
|
||||
font-size: 2em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.widget-title h2 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.widget-rating {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.widget-textarea {
|
||||
color: var(--bulma-body-color);
|
||||
background-color: var(--bulma-text-20);
|
||||
width: 95%;
|
||||
border-radius: 30px;
|
||||
border: none;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.textarea-normal {
|
||||
height: 80px;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.textarea-expanded {
|
||||
height: 350px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.transition-height {
|
||||
transition: height 0.5s;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
|
||||
.widget-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.widget-title {
|
||||
font-size: 1.5em;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -122,11 +122,7 @@ export async function putStartReadDate(bookId, startdate) {
|
||||
return genericPayloadCall('/ws/book/' + bookId, { startDate: startdate }, 'PUT')
|
||||
}
|
||||
|
||||
export async function putWantReadBook(bookId, payload) {
|
||||
return genericPayloadCall('/ws/book/' + bookId, payload, 'PUT')
|
||||
}
|
||||
|
||||
export async function putRateBook(bookId, payload) {
|
||||
export async function putUpdateBook(bookId, payload) {
|
||||
return genericPayloadCall('/ws/book/' + bookId, payload, 'PUT')
|
||||
}
|
||||
|
||||
|
||||
@@ -81,5 +81,9 @@
|
||||
"releasedate": "Release date:",
|
||||
"publisher": "Publisher:",
|
||||
"importing": "Importing..."
|
||||
},
|
||||
"review": {
|
||||
"title": "My review",
|
||||
"textplaceholder": "Write my review..."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,5 +81,9 @@
|
||||
"releasedate": "Date de publication : ",
|
||||
"publisher": "Maison d'édition : ",
|
||||
"importing": "Import en cours..."
|
||||
},
|
||||
"review": {
|
||||
"title": "Ma critique",
|
||||
"textplaceholder": "Écrire ma critique..."
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user