Book form: add a field to write a review
This commit is contained in:
@@ -3,8 +3,7 @@ import { ref, computed } from 'vue'
|
|||||||
import {
|
import {
|
||||||
getBook,
|
getBook,
|
||||||
getImagePathOrDefault,
|
getImagePathOrDefault,
|
||||||
putWantReadBook,
|
putUpdateBook,
|
||||||
putRateBook,
|
|
||||||
putStartReadDate,
|
putStartReadDate,
|
||||||
putStartReadDateUnset,
|
putStartReadDateUnset,
|
||||||
putEndReadDate,
|
putEndReadDate,
|
||||||
@@ -14,7 +13,7 @@ import {
|
|||||||
import { useRouter, onBeforeRouteUpdate } from 'vue-router'
|
import { useRouter, onBeforeRouteUpdate } from 'vue-router'
|
||||||
import { VRating } from 'vuetify/components/VRating'
|
import { VRating } from 'vuetify/components/VRating'
|
||||||
import BookFormIcons from './BookFormIcons.vue'
|
import BookFormIcons from './BookFormIcons.vue'
|
||||||
import ReviewModal from './ReviewModal.vue'
|
import ReviewWidget from './ReviewWidget.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -37,7 +36,12 @@ function onRatingUpdate(rating) {
|
|||||||
data.value.read = true
|
data.value.read = true
|
||||||
data.value.wantread = false
|
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() {
|
async function onReadIconClick() {
|
||||||
@@ -53,7 +57,7 @@ async function onReadIconClick() {
|
|||||||
|
|
||||||
function onWantReadIconClick() {
|
function onWantReadIconClick() {
|
||||||
data.value.wantread = !data.value.wantread
|
data.value.wantread = !data.value.wantread
|
||||||
putWantReadBook(props.id, { wantread: data.value.wantread })
|
putUpdateBook(props.id, { wantread: data.value.wantread })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onStartReadIconClick() {
|
async function onStartReadIconClick() {
|
||||||
@@ -100,18 +104,6 @@ function goToAuthor() {
|
|||||||
<figure class="image">
|
<figure class="image">
|
||||||
<img v-bind:src="imagePathOrDefault" v-bind:alt="data.title" />
|
<img v-bind:src="imagePathOrDefault" v-bind:alt="data.title" />
|
||||||
</figure>
|
</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>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<h3 class="title">{{ data.title }}</h3>
|
<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.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.inventaireid">Inventaire ID: {{ data.inventaireid }}</div>
|
||||||
<div class="my-5" v-if="data.openlibraryid">OLID: {{ data.openlibraryid }}</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>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<BookFormIcons
|
<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')
|
return genericPayloadCall('/ws/book/' + bookId, { startDate: startdate }, 'PUT')
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function putWantReadBook(bookId, payload) {
|
export async function putUpdateBook(bookId, payload) {
|
||||||
return genericPayloadCall('/ws/book/' + bookId, payload, 'PUT')
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function putRateBook(bookId, payload) {
|
|
||||||
return genericPayloadCall('/ws/book/' + bookId, payload, 'PUT')
|
return genericPayloadCall('/ws/book/' + bookId, payload, 'PUT')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,5 +81,9 @@
|
|||||||
"releasedate": "Release date:",
|
"releasedate": "Release date:",
|
||||||
"publisher": "Publisher:",
|
"publisher": "Publisher:",
|
||||||
"importing": "Importing..."
|
"importing": "Importing..."
|
||||||
|
},
|
||||||
|
"review": {
|
||||||
|
"title": "My review",
|
||||||
|
"textplaceholder": "Write my review..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,5 +81,9 @@
|
|||||||
"releasedate": "Date de publication : ",
|
"releasedate": "Date de publication : ",
|
||||||
"publisher": "Maison d'édition : ",
|
"publisher": "Maison d'édition : ",
|
||||||
"importing": "Import en cours..."
|
"importing": "Import en cours..."
|
||||||
|
},
|
||||||
|
"review": {
|
||||||
|
"title": "Ma critique",
|
||||||
|
"textplaceholder": "Écrire ma critique..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user