Books list: make the buttons work like in the form

This commit is contained in:
2026-03-26 17:07:22 +01:00
parent 4d687e3dcb
commit d8fc7396ff
6 changed files with 111 additions and 27 deletions

View File

@@ -6,6 +6,7 @@ import {
putUpdateBook, putUpdateBook,
putStartReadDate, putStartReadDate,
putStartReadDateUnset, putStartReadDateUnset,
putReadBook,
putEndReadDate, putEndReadDate,
putEndReadDateUnset, putEndReadDateUnset,
putUnreadBook, putUnreadBook,
@@ -49,7 +50,7 @@ async function onReadIconClick() {
if (data.value.read) { if (data.value.read) {
data.value.wantread = false data.value.wantread = false
data.value.endReadDate = today data.value.endReadDate = today
putEndReadDate(props.id, today) putReadBook(props.id)
} else { } else {
putUnreadBook(props.id) putUnreadBook(props.id)
} }

View File

@@ -1,6 +1,14 @@
<script setup> <script setup>
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { putReadBook, getImagePathOrDefault, postImportBook } from './api.js' import {
putUpdateBook,
putReadBook,
putUnreadBook,
putStartRead,
putStartReadDateUnset,
getImagePathOrDefault,
postImportBook,
} from './api.js'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const router = useRouter() const router = useRouter()
@@ -14,17 +22,87 @@ const props = defineProps({
description: String, description: String,
rating: Number, rating: Number,
read: Boolean, read: Boolean,
startread: Boolean, startreaddate: String,
wantread: Boolean, wantread: Boolean,
coverPath: String, coverPath: String,
}) })
const imagePathOrDefault = computed(() => getImagePathOrDefault(props.coverPath)) const imagePathOrDefault = computed(() => getImagePathOrDefault(props.coverPath))
const error = ref(null) const error = ref(null)
const isWantRead = ref(props.wantread)
const currentStartReadDate = ref(props.startreaddate)
const isRead = ref(props.read)
const today = new Date().toISOString().slice(0, 10)
const isStartRead = computed(
() => currentStartReadDate && currentStartReadDate.value && isRead && !isRead.value,
)
async function onUserBookRead() { async function onUserBookRead() {
if (!isRead.value) {
userBookRead()
} else {
userBookUnread()
}
}
async function userBookRead() {
const res = await putReadBook(props.id) const res = await putReadBook(props.id)
if (res.ok) { if (res.ok) {
router.push('/books') currentStartReadDate.value = ''
isWantRead.value = false
isRead.value = true
} else {
res.json().then((json) => (error.value = json))
}
}
async function userBookUnread() {
const res = await putUnreadBook(props.id)
if (res.ok) {
isRead.value = false
} else {
res.json().then((json) => (error.value = json))
}
}
async function onUserBookWantRead() {
const res = await putUpdateBook(props.id, { wantread: !isWantRead.value })
if (res.ok) {
isWantRead.value = !isWantRead.value
} else {
res.json().then((json) => (error.value = json))
}
}
async function onUserBookStartRead() {
if (!isStartRead.value) {
userBookStartRead()
} else {
userBookCancelStartRead()
}
}
async function userBookStartRead() {
const res = await putStartRead(props.id)
if (!res.ok) {
res.json().then((json) => (error.value = json))
return
}
const resp = await putUnreadBook(props.id)
if (resp.ok) {
currentStartReadDate.value = today
isRead.value = false
} else {
resp.json().then((json) => (error.value = json))
}
}
async function userBookCancelStartRead() {
const res = await putStartReadDateUnset(props.id)
if (res.ok) {
currentStartReadDate.value = ''
} else { } else {
res.json().then((json) => (error.value = json)) res.json().then((json) => (error.value = json))
} }
@@ -69,21 +147,21 @@ async function importInventaireEdition(inventaireid) {
</div> </div>
</div> </div>
<div v-if="id && id != 0" class="column is-narrow"> <div v-if="id && id != 0" class="column is-narrow">
<button @click="" class="button is-large verticalbutton"> <button @click="onUserBookWantRead" class="button is-large verticalbutton">
<span class="icon" :title="$t('booklistelement.wantread')"> <span class="icon" :title="$t('booklistelement.wantread')">
<b-icon-eye-fill v-if="props.wantread" /> <b-icon-eye-fill v-if="isWantRead" />
<b-icon-eye v-else /> <b-icon-eye v-else />
</span> </span>
</button> </button>
<button @click="" class="button is-large verticalbutton"> <button @click="onUserBookStartRead" class="button is-large verticalbutton">
<span class="icon" :title="$t('booklistelement.startread')"> <span class="icon" :title="$t('booklistelement.startread')">
<b-icon-book-fill v-if="props.startread" /> <b-icon-book-fill v-if="isStartRead" />
<b-icon-book v-else /> <b-icon-book v-else />
</span> </span>
</button> </button>
<button @click="onUserBookRead" class="button is-large verticalbutton"> <button @click="onUserBookRead" class="button is-large verticalbutton">
<span class="icon" :title="$t('booklistelement.read')"> <span class="icon" :title="$t('booklistelement.read')">
<b-icon-check-circle-fill v-if="props.read" /> <b-icon-check-circle-fill v-if="isRead" />
<b-icon-check-circle v-else /> <b-icon-check-circle v-else />
</span> </span>
</button> </button>

View File

@@ -107,7 +107,7 @@ export async function postImportBook(id, language) {
} }
export async function putReadBook(bookId) { export async function putReadBook(bookId) {
return genericPayloadCall('/ws/book/' + bookId, { read: true }, 'PUT') return putEndReadDate(bookId, new Date().toISOString().slice(0, 10))
} }
export async function putUnreadBook(bookId) { export async function putUnreadBook(bookId) {
@@ -126,6 +126,10 @@ export async function putStartReadDateUnset(bookId) {
return genericPayloadCall('/ws/book/' + bookId, { startDate: 'null' }, 'PUT') return genericPayloadCall('/ws/book/' + bookId, { startDate: 'null' }, 'PUT')
} }
export async function putStartRead(bookId) {
return putStartReadDate(bookId, new Date().toISOString().slice(0, 10))
}
export async function putStartReadDate(bookId, startdate) { export async function putStartReadDate(bookId, startdate) {
return genericPayloadCall('/ws/book/' + bookId, { startDate: startdate }, 'PUT') return genericPayloadCall('/ws/book/' + bookId, { startDate: startdate }, 'PUT')
} }

View File

@@ -47,6 +47,7 @@ func TestSearchBook_OneBookRead(t *testing.T) {
ID: 4, ID: 4,
Rating: 7, Rating: 7,
Read: true, Read: true,
StartReadDate: "2026-01-30",
WantRead: false, WantRead: false,
CoverPath: "/static/bookcover/lesdieuxontsoif.jpg", CoverPath: "/static/bookcover/lesdieuxontsoif.jpg",
}}, }},
@@ -63,7 +64,7 @@ func TestSearchBook_OneBookStartRead(t *testing.T) {
ID: 30, ID: 30,
Rating: 0, Rating: 0,
Read: false, Read: false,
StartRead: true, StartReadDate: "2025-11-22",
WantRead: false, WantRead: false,
CoverPath: "/static/bookcover/Recherches-philosophiques.jpg", CoverPath: "/static/bookcover/Recherches-philosophiques.jpg",
}}, }},

View File

@@ -53,7 +53,7 @@ type BookSearchGetBook struct {
IsInventaireEdition bool `json:"isinventaireedition"` IsInventaireEdition bool `json:"isinventaireedition"`
Rating int `json:"rating"` Rating int `json:"rating"`
Read bool `json:"read"` Read bool `json:"read"`
StartRead bool `json:"startread"` StartReadDate string `json:"startreaddate"`
WantRead bool `json:"wantread"` WantRead bool `json:"wantread"`
CoverPath string `json:"coverPath"` CoverPath string `json:"coverPath"`
} }

View File

@@ -171,7 +171,7 @@ func fetchBookSearchQuery(db *gorm.DB, userId uint, searchterm string) *gorm.DB
func fetchBookQueryBuilder(db *gorm.DB, userId uint) *gorm.DB { func fetchBookQueryBuilder(db *gorm.DB, userId uint) *gorm.DB {
query := db.Model(&model.Book{}) query := db.Model(&model.Book{})
query = query.Select("books.id, books.title, authors.name as author, books.small_description as description, books.inventaire_id, user_books.rating, user_books.read, (user_books.start_read_date IS NOT NULL AND (user_books.read IS NULL OR user_books.read IS FALSE)) as start_read, user_books.want_read, " + selectStaticFilesPath()) query = query.Select("books.id, books.title, authors.name as author, books.small_description as description, books.inventaire_id, user_books.rating, user_books.read, DATE(user_books.start_read_date) as start_read_date, user_books.want_read, " + selectStaticFilesPath())
query = joinAuthors(query) query = joinAuthors(query)
query = query.Joins("left join user_books on (user_books.book_id = books.id and user_books.user_id = ?)", userId) query = query.Joins("left join user_books on (user_books.book_id = books.id and user_books.user_id = ?)", userId)
query = joinStaticFiles(query) query = joinStaticFiles(query)