added delete form
This commit is contained in:
parent
2c0054671e
commit
68d3b86816
@ -2,7 +2,9 @@
|
|||||||
import { useAuth } from '@/composables/useAuth';
|
import { useAuth } from '@/composables/useAuth';
|
||||||
import type { Book } from '@/models/book';
|
import type { Book } from '@/models/book';
|
||||||
import { convertState } from '@/utils';
|
import { convertState } from '@/utils';
|
||||||
import { ref } from 'vue';
|
import { ref, Transition } from 'vue';
|
||||||
|
|
||||||
|
const emit = defineEmits(["openDeleteModal", "concernedBook"]);
|
||||||
|
|
||||||
const API_URL = import.meta.env.VITE_MBL_API_URL
|
const API_URL = import.meta.env.VITE_MBL_API_URL
|
||||||
const props = defineProps<{ book: Book }>()
|
const props = defineProps<{ book: Book }>()
|
||||||
@ -42,10 +44,17 @@ function unshowButtonOnLeave() {
|
|||||||
<div :class="props.book.state" class="state-indicator"></div>
|
<div :class="props.book.state" class="state-indicator"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="isAuthenticated" @mouseover="showButtonsOnHover" @mouseleave="unshowButtonOnLeave" class="chevron"><font-awesome-icon icon="fa-chevron-down" /></p>
|
<div class="chevron-div" @mouseover="showButtonsOnHover" @mouseleave="unshowButtonOnLeave">
|
||||||
<div class="buttons" v-show="showButtons">
|
<p v-if="isAuthenticated" style="text-align: center;">
|
||||||
<button>Modifier</button>
|
<font-awesome-icon :class="{ 'chevron-go-up': showButtons, 'chevron-go-down': !showButtons }"
|
||||||
<button>Supprimer</button>
|
icon="fa-chevron-down" />
|
||||||
|
</p>
|
||||||
|
<Transition>
|
||||||
|
<div class="buttons" v-show="showButtons">
|
||||||
|
<button class="update-button">Modifier</button>
|
||||||
|
<button @click="$emit('openDeleteModal'); $emit('concernedBook', props.book)" class="delete-button"><font-awesome-icon icon="fa-trash-can" /></button>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -67,11 +76,51 @@ function unshowButtonOnLeave() {
|
|||||||
background-color: #832f30;
|
background-color: #832f30;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v-enter-active,
|
||||||
|
.v-leave-active {
|
||||||
|
transition: opacity 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-enter-from,
|
||||||
|
.v-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-button {
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
min-width: 13%;
|
||||||
|
background-color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-button {
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #1e90ff;
|
||||||
|
min-width: 85%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-button:hover {
|
||||||
|
transition: 0.3s;
|
||||||
|
background-color: #c53838;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-button:hover {
|
||||||
|
transition: 0.3s;
|
||||||
|
background-color: #176cc0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-button, .update-button {
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.buttons {
|
.buttons {
|
||||||
|
margin: 1em;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
gap: 1em;
|
justify-content: space-between;
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.state-div {
|
.state-div {
|
||||||
@ -105,16 +154,22 @@ function unshowButtonOnLeave() {
|
|||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chevron {
|
.chevron-div {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
min-width: 100%;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
transition: background-color 0.5s ease, transform 0.5s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chevron:hover {
|
.chevron-go-up {
|
||||||
|
transition: background-color 0.5s ease, transform 0.5s ease;
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chevron-go-down {
|
||||||
|
transition: background-color 0.5s ease, transform 0.5s ease;
|
||||||
|
transform: rotate(0);
|
||||||
|
}
|
||||||
|
|
||||||
.card a {
|
.card a {
|
||||||
text-decoration: none
|
text-decoration: none
|
||||||
}
|
}
|
||||||
|
|||||||
112
src/components/DeleteBookForm.vue
Normal file
112
src/components/DeleteBookForm.vue
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { Book } from "@/models/book";
|
||||||
|
import { BookService } from "@/services/book.service";
|
||||||
|
|
||||||
|
const props = defineProps<{ isActive: boolean; concernedBook: Book | undefined }>();
|
||||||
|
const emit = defineEmits(["closeDeleteBookModal", "isDeleted"]);
|
||||||
|
|
||||||
|
const bookService = new BookService();
|
||||||
|
|
||||||
|
async function deleteBook() {
|
||||||
|
if (props.concernedBook?.id) {
|
||||||
|
try {
|
||||||
|
await bookService.deleteBookById(props.concernedBook.id);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit("isDeleted", true);
|
||||||
|
emit("closeDeleteBookModal");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="props.isActive ? 'opened-modal' : 'closed-modal'">
|
||||||
|
<div class="overlay" @click="$emit('closeDeleteBookModal')"></div>
|
||||||
|
|
||||||
|
<div class="modale card">
|
||||||
|
<button class="btn-close" @click="$emit('closeDeleteBookModal')">X</button>
|
||||||
|
|
||||||
|
<h2>Êtes vous sûr de supprimer le livre '{{ props.concernedBook?.title }}'</h2>
|
||||||
|
<button @click="deleteBook" class="send">Oui</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.opened-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closed-modal {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modale {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
background-color: light-dark(#ffffff, #282828);
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 500px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: light-dark(#868686, #999999);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-close:hover {
|
||||||
|
transition: 0.3s;
|
||||||
|
color: light-dark(black, white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: #ef4444;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send:hover {
|
||||||
|
transition: 0.5s;
|
||||||
|
background-color: #c53838;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -7,11 +7,13 @@ import { library } from "@fortawesome/fontawesome-svg-core";
|
|||||||
import { faArrowRightFromBracket } from "@fortawesome/free-solid-svg-icons";
|
import { faArrowRightFromBracket } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { faChevronDown } from "@fortawesome/free-solid-svg-icons";
|
import { faChevronDown } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { faTrashCan } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||||
|
|
||||||
library.add(faArrowRightFromBracket);
|
library.add(faArrowRightFromBracket);
|
||||||
library.add(faPlus);
|
library.add(faPlus);
|
||||||
library.add(faChevronDown);
|
library.add(faChevronDown);
|
||||||
|
library.add(faTrashCan);
|
||||||
|
|
||||||
createApp(App)
|
createApp(App)
|
||||||
.use(router)
|
.use(router)
|
||||||
|
|||||||
@ -6,7 +6,7 @@ export class AuthenticationService {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const apiURL = import.meta.env.VITE_MBL_API_URL;
|
const apiURL = import.meta.env.VITE_MBL_API_URL;
|
||||||
|
|
||||||
this.client = axios.create({
|
this.client = axios.create({
|
||||||
baseURL: `${apiURL}/api/`,
|
baseURL: `${apiURL}/api/`,
|
||||||
headers: {
|
headers: {
|
||||||
@ -38,7 +38,7 @@ export class AuthenticationService {
|
|||||||
originalRequest._retry = true;
|
originalRequest._retry = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const newData = await this.refreshToken();
|
const newData = await this.refreshToken();
|
||||||
|
|
||||||
sessionStorage.setItem("access", newData.access);
|
sessionStorage.setItem("access", newData.access);
|
||||||
originalRequest.headers.Authorization = `Bearer ${newData.access}`;
|
originalRequest.headers.Authorization = `Bearer ${newData.access}`;
|
||||||
@ -57,7 +57,6 @@ export class AuthenticationService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async findUsers() {
|
async findUsers() {
|
||||||
return await this.client.get("users").then((res) => {
|
return await this.client.get("users").then((res) => {
|
||||||
return res.data as User;
|
return res.data as User;
|
||||||
@ -65,7 +64,6 @@ export class AuthenticationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async findMe() {
|
async findMe() {
|
||||||
|
|
||||||
return await this.client
|
return await this.client
|
||||||
.get("profile")
|
.get("profile")
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
@ -79,7 +77,7 @@ export class AuthenticationService {
|
|||||||
async refreshToken() {
|
async refreshToken() {
|
||||||
const refreshToken = localStorage.getItem("refresh");
|
const refreshToken = localStorage.getItem("refresh");
|
||||||
const apiURL = import.meta.env.VITE_MBL_API_URL;
|
const apiURL = import.meta.env.VITE_MBL_API_URL;
|
||||||
|
|
||||||
return await axios
|
return await axios
|
||||||
.post(`${apiURL}/api/token/refresh/`, {
|
.post(`${apiURL}/api/token/refresh/`, {
|
||||||
refresh: refreshToken,
|
refresh: refreshToken,
|
||||||
@ -105,4 +103,4 @@ export class AuthenticationService {
|
|||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,7 +55,7 @@ export class BookService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findBooks() {
|
async findBooks() {
|
||||||
return await this.client
|
return await this.client
|
||||||
.get("")
|
.get("")
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
@ -66,6 +66,12 @@ async findBooks() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteBookById(id: number) {
|
||||||
|
return await this.client.delete(`/${id}/`).catch((error) => {
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async createBook(formData: FormData) {
|
async createBook(formData: FormData) {
|
||||||
return await this.client
|
return await this.client
|
||||||
.post("", formData)
|
.post("", formData)
|
||||||
|
|||||||
@ -6,10 +6,13 @@ import BookCard from "@/components/BookCard.vue";
|
|||||||
import CreateBookForm from "@/components/CreateBookForm.vue";
|
import CreateBookForm from "@/components/CreateBookForm.vue";
|
||||||
import { useAuth } from "@/composables/useAuth";
|
import { useAuth } from "@/composables/useAuth";
|
||||||
import { useToast } from "vue-toast-notification";
|
import { useToast } from "vue-toast-notification";
|
||||||
|
import DeleteBookForm from "@/components/DeleteBookForm.vue";
|
||||||
|
|
||||||
const books = ref<Book[]>([]);
|
const books = ref<Book[]>([]);
|
||||||
const bookService = new BookService();
|
const bookService = new BookService();
|
||||||
const isCreatingBookFormOpened = ref<boolean>(false);
|
const isCreatingBookFormOpened = ref<boolean>(false);
|
||||||
|
const isDeletingBookFormOpened = ref<boolean>(false);
|
||||||
|
const deletingBook = ref<Book | undefined>(undefined);
|
||||||
const { isAuthenticated } = useAuth();
|
const { isAuthenticated } = useAuth();
|
||||||
const toastService = useToast();
|
const toastService = useToast();
|
||||||
|
|
||||||
@ -38,6 +41,22 @@ function closeBookFormModal() {
|
|||||||
isCreatingBookFormOpened.value = false;
|
isCreatingBookFormOpened.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closeDeleteBookFormModal() {
|
||||||
|
isDeletingBookFormOpened.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteBookFromList(bool: boolean) {
|
||||||
|
if (deletingBook.value && bool) {
|
||||||
|
books.value = books.value.filter((book) => book.id !== deletingBook.value?.id);
|
||||||
|
deletingBook.value = undefined;
|
||||||
|
toastService.success(`Livre supprimé avec succès"`, {
|
||||||
|
position: "bottom-right",
|
||||||
|
pauseOnHover: true,
|
||||||
|
dismissible: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
books.value = await bookService.findBooks();
|
books.value = await bookService.findBooks();
|
||||||
|
|
||||||
@ -57,8 +76,22 @@ onMounted(async () => {
|
|||||||
@new-book="(book) => pushNewBook(book)"
|
@new-book="(book) => pushNewBook(book)"
|
||||||
@close-book-form-modal="closeBookFormModal"
|
@close-book-form-modal="closeBookFormModal"
|
||||||
/>
|
/>
|
||||||
|
<DeleteBookForm
|
||||||
|
v-if="isAuthenticated"
|
||||||
|
:is-active="isDeletingBookFormOpened"
|
||||||
|
:concerned-book="deletingBook"
|
||||||
|
@is-deleted="(bool) => deleteBookFromList(bool)"
|
||||||
|
@close-delete-book-modal="closeDeleteBookFormModal"
|
||||||
|
/>
|
||||||
<main class="cards">
|
<main class="cards">
|
||||||
<BookCard v-for="book in books" :book="book" :key="book.id" class="card" />
|
<BookCard
|
||||||
|
v-for="book in books"
|
||||||
|
:book="book"
|
||||||
|
:key="book.id"
|
||||||
|
@concerned-book="(book) => (deletingBook = book)"
|
||||||
|
@open-delete-modal="isDeletingBookFormOpened = true"
|
||||||
|
class="card"
|
||||||
|
/>
|
||||||
</main>
|
</main>
|
||||||
<div v-if="isAuthenticated" class="add-button-container">
|
<div v-if="isAuthenticated" class="add-button-container">
|
||||||
<button @click="isCreatingBookFormOpened = true" class="add-button">
|
<button @click="isCreatingBookFormOpened = true" class="add-button">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user