Files
DAV/pages/index.vue
Do-raa 1b35e137d3
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
ui enhancement
2024-12-23 19:23:36 +01:00

530 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<v-container fluid>
<!-- Loading Section -->
<v-row v-if="isLoading" class="justify-center">
<Loader />
</v-row>
<!-- Main Content Section -->
<v-row v-else class="mx-5">
<v-row class="video-section">
<video autoplay loop muted playsinline class="video-background">
<source src="/videos/video.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
</v-row>
<!-- Introduction section -->
<v-row v-if="introduction" class="d-flex justify-center align-center text-center">
<h1 class="my-8 lg:text-xl text-l text-black">{{ introduction.title }}</h1>
<p class="text-xs text-black sm:mb-24 mr-2 lg:mr-8 text-justify">{{ introduction.description }}</p>
</v-row>
<!-- Articles Section -->
<v-row>
<!-- Left Column -->
<v-col cols="12" md="6">
<div v-for="(article, index) in leftColumnArticles" :key="article.id || index" class="article">
<v-card class="mb-12" elevation="0">
<v-card-title>
<h2 class="lg:text-sm text-xs text-wrap text-black font-bold">{{ article.title }}</h2>
</v-card-title>
<v-card-text>
<p class="text-xs text-black">{{ article.description }}</p>
</v-card-text>
<v-card-actions>
<v-btn icon="mdi-play" @click="toggleShow(article.id)">
</v-btn>
<p class="text-gray-700 lg:text-xs text-xs">Suggérer une modification de texte</p>
</v-card-actions>
<v-expand-transition>
<div v-show="showStates[article.id]">
<v-divider></v-divider>
<p class="text-gray-500 text-xs mx-5"> La constitution des DAV ne se fera pas
seule. Chacun et
chacune sont
invité(e)s à donner le meilleur deux même pour assurer sa constitution,
son respect, ainsi que sa mise en application.
Soyez courtois . Tout commentaire désobligeant irrespectueux ou agressif sera
systématiquement supprimé. </p>
<!-- Comments Section -->
<v-card-text>
<v-textarea v-model="newComments[article.id]" outlined rows="2"
auto-grow></v-textarea>
<VBtnValid @click="addComment(article.id)">
Valider
</VBtnValid>
</v-card-text>
</div>
</v-expand-transition>
</v-card>
</div>
</v-col>
<!-- Right Column -->
<v-col cols="12" md="6">
<div v-for="(article, index) in rightColumnArticles" :key="article.id || index" class="article">
<v-card class="mb-12" elevation="0">
<v-card-title>
<h2 class="lg:text-sm text-xs text-wrap text-black font-bold">{{ article.title }}</h2>
</v-card-title>
<v-card-text>
<p class="text-xs text-black">{{ article.description }}</p>
</v-card-text>
<v-card-actions>
<v-btn icon="mdi-play" @click="toggleShow(article.id)">
</v-btn>
<p class="text-gray-700 lg:text-xs text-xs">Suggérer une modification de texte</p>
</v-card-actions>
<v-expand-transition>
<div v-show="showStates[article.id]">
<v-divider></v-divider>
<p class="text-gray-500 text-xs mx-5"> La constitution des DAV ne se fera pas
seule. Chacun et
chacune sont
invité(e)s à donner le meilleur deux même pour assurer sa constitution,
son respect, ainsi que sa mise en application.
Soyez courtois . Tout commentaire désobligeant irrespectueux ou agressif sera
systématiquement supprimé. </p>
<!-- Comments Section -->
<v-card-text>
<v-textarea v-model="newComments[article.id]" outlined rows="2"
auto-grow></v-textarea>
<VBtnValid @click="addComment(article.id)">
Valider
</VBtnValid>
</v-card-text>
</div>
</v-expand-transition>
</v-card>
</div>
</v-col>
</v-row>
<v-row v-if="summary" class="d-flex justify-center align-center text-center">
<p class="text-xs text-black mb-8 px-1 text-center">{{ summary.description }}</p>
</v-row>
<v-row class="justify-center">
<v-col cols="12" md="6" lg="8">
<v-expansion-panels variant="accordion">
<v-expansion-panel v-for="(article, index) in articles" :key="article.id" class="article">
<v-expansion-panel-title class="d-flex flex-column align-start" expand-icon=""
collapse-icon="">
<div class="flex items-center mb-0">
<!-- Like/Dislike Section -->
<div class="mr-3 flex items-center">
<v-icon class="mr-1 text-xs" size="18" color="yellow"
@click.stop="likeArticle(article.id)">
mdi-thumb-up
</v-icon>
<p class="text-gray-400 text-sm">{{ article.likes }}</p>
<v-icon class="mr-1 ml-2 text-xs" size="18" color="yellow"
@click.stop="dislikeArticle(article.id)">
mdi-thumb-down
</v-icon>
<p class="text-gray-400 text-sm">{{ article.dislikes }}</p>
</div>
<h2 class="text-sm text-wrap text-black font-bold m-0">
{{ article.title }}
</h2>
</div>
<div class="d-flex align-center mt-2">
<!-- Control the panel opening -->
<v-icon class="mr-1">
mdi-play
</v-icon>
<p class="text-gray-700 text-xs ml-2">Voir les suggestions</p>
</div>
</v-expansion-panel-title>
<v-expansion-panel-text>
<v-timeline align="start" density="compact" class="ml-3">
<v-timeline-item v-if="article.comments.length"
v-for="(comment, cIndex) in article.comments" :key="cIndex"
dot-color="secondary" size="x-small">
<div class="flex items-center mb-0">
<!-- Like/Dislike Section -->
<div class="mr-3 flex items-center">
<v-icon color="yellow" size="14"
@click="likeComment(article.id, comment.id)">mdi-thumb-up</v-icon>
<span class="text-xs text-gray-400 ml-1 mr-2">{{ comment.likes }}</span>
<v-icon color="yellow" size="14"
@click="dislikeComment(article.id, comment.id)">mdi-thumb-down</v-icon>
<span class="text-xs text-gray-400 ml-1">{{ comment.dislikes }}</span>
</div>
<!-- Comment Content -->
<p class="text-sm text-black">{{ comment.content }}</p>
</div>
</v-timeline-item>
<!-- No Comments Placeholder -->
<v-timeline-item v-else dot-color="secondary" size="x-small">
<p class="text-sm text-black">Aucune suggestion pour cet article</p>
</v-timeline-item>
</v-timeline>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</v-col>
</v-row>
<!-- Signature Section -->
<v-row class="justify-center w-full">
<v-col cols="12" md="6" lg="8">
<v-card elevation="3" class="pa-6 bg-gray-100" color="secondary">
<v-card-title class="text-center">
<h2 class="text-uppercase text-md font-semibold font-sans">Signer la Charte</h2>
</v-card-title>
<v-divider></v-divider>
<v-card-text>
<v-form @submit.prevent="submitSignature" lazy-validation class="text-xs font-sans">
<v-text-field v-model="name" label="Nom / prénom / pseudonyme" outlined dense
hide-details :rules="[rules.required]" class="mb-2"
prepend-inner-icon="mdi-account"></v-text-field>
<v-text-field v-model="email" type="email" label="Email"
placeholder="Entrez votre email" outlined dense hide-details
:rules="[rules.required, rules.email]" class="mb-2"
prepend-inner-icon="mdi-email"></v-text-field>
<v-textarea v-model="comment" label="Ajouter un commentaire"
placeholder="Partagez vos réflexions ou suggestions" outlined dense hide-details
rows="2" class="mb-2" prepend-inner-icon="mdi-message-text"></v-textarea>
<VBtnValid type="submit" block>
<v-icon size="16" left>mdi-check-circle</v-icon>
Signer
</VBtnValid>
</v-form>
</v-card-text>
</v-card>
</v-col>
</v-row>
<!-- Signature Display Section -->
<v-row class="justify-center">
<v-col cols="12" md="6" lg="8">
<v-expansion-panels variant="accordion">
<v-expansion-panel>
<v-expansion-panel-title class="d-flex flex-column align-start" expand-icon=""
color="primary" collapse-icon="">
<div class="text-uppercase text-l font-semibold font-sans">voir les signatures</div>
</v-expansion-panel-title>
<v-expansion-panel-text>
<v-timeline align="start" density="compact">
<v-timeline-item v-for="signature in signatures" :key="signature.id"
dot-color="secondary" size="x-small">
<div>
<div class="font-weight-normal font-sans text-sm">
<strong>{{ signature.name }}</strong> @{{ signature.createdAt }}
</div>
<div class="font-weight-normal font-sans text-sm">{{ signature.comment }}
</div>
</div>
</v-timeline-item>
</v-timeline>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</v-col>
</v-row>
<!-- Success Overlay (Dynamic Content) -->
<v-overlay v-model="overlay.visible" class="d-flex align-center justify-center"
:style="{ background: 'rgba(0, 0, 0, 0.5)', backdropFilter: 'blur(4px)' }">
<v-card class="py-10 px-8 text-center" elevation="4" rounded="lg" style="max-width: 400px;">
<v-icon :color="overlay.iconColor" size="48" class="mb-4">{{ overlay.icon }}</v-icon>
<p class="overlay-msg text-sm font-semibold text-black text-uppercase">{{ overlay.message }}</p>
<VBtnValid class="mt-6" @click="overlay.visible = false">
{{ overlay.buttonText }}
</VBtnValid>
</v-card>
</v-overlay>
</v-row>
</v-container>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useAsyncData } from 'nuxt/app';
import Loader from '~/components/Loader.vue';
import { updateLikeDislike } from '~/lib/likes.ts';
import { fetchArticlesData, fetchCommentsData, fetchSignatures } from '~/lib/fetchers';
import { showOverlay } from '~/lib/overlayHandler';
definePageMeta({
layout: 'default',
});
const articles = ref([]);
const newComments = ref([]);
const leftColumnArticles = ref([]);
const rightColumnArticles = ref([]);
const showStates = ref({});
const introduction = ref(null);
const summary = ref(null);
const signatures = ref([]);
// Form fields
const name = ref('');
const email = ref('');
const comment = ref('');
const isFormValid = computed(() => name.value && email.value);
const isLoading = ref(true);
const overlay = ref({
icon: '',
message: '',
buttonText: '',
iconColor: '',
visible: false,
})
const rules = {
required: (value) => !!value || "Ce champ est obligatoire.",
email: (value) => /.+@.+\..+/.test(value) || "L'email doit être valide.",
};
const toggleShow = (articleId) => {
showStates.value[articleId] = !showStates.value[articleId];
};
// Fetch articles data
const fetchContent = async () => {
try {
const { data: contentData, error } = await useAsyncData('content', () =>
queryContent().find()
);
// Process fetched content
if (!error.value && contentData.value) {
for (const file of contentData.value) {
if (file.type === "intro") {
introduction.value = file;
} else if (file.type === "article") {
const article = {
id: file.id,
title: file.title,
description: file.description,
likes: file.likes || 0,
dislikes: file.dislikes || 0,
comments: file.comments || [],
};
// Add the article locally
articles.value.push(article);
await $fetch('/api/articles', {
method: 'POST',
body: {
id: article.id,
title: article.title,
description: article.description,
likes: article.likes,
dislikes: article.dislikes
},
});
} else if (file.type === "summary") {
summary.value = file;
}
}
}
// Split articles into left and right columns
// leftColumnArticles.value = articles.value.filter((_, index) => index % 2 === 0);
// rightColumnArticles.value = articles.value.filter((_, index) => index % 2 !== 0);
// Split articles into two columns (assuming a 50% split)
const midIndex = Math.ceil(articles.value.length / 2);
leftColumnArticles.value = articles.value.slice(0, midIndex);
rightColumnArticles.value = articles.value.slice(midIndex);
// Initialize newComments object based on article IDs
articles.value.forEach(article => {
newComments.value[article.id] = '';
});
}
catch (err) {
console.error('Error fetching content:', err);
} finally {
isLoading.value = false;
}
};
onMounted(async () => {
await fetchContent()
await fetchArticlesData(articles);
await fetchCommentsData(articles);
await fetchSignatures(signatures);
});
const likeArticle = (articleId) => {
updateLikeDislike({
type: 'article',
action: 'like',
id: articleId,
articles,
});
};
const dislikeArticle = (articleId) => {
updateLikeDislike({
type: 'article',
action: 'dislike',
id: articleId,
articles,
});
};
const likeComment = (articleId, commentId) => {
updateLikeDislike({
type: 'comment',
action: 'like',
id: commentId,
articleId,
articles,
});
};
const dislikeComment = (articleId, commentId) => {
updateLikeDislike({
type: 'comment',
action: 'dislike',
id: commentId,
articleId,
articles,
});
};
const addComment = async (articleId) => {
const commentText = newComments.value[articleId]?.trim();
if (!commentText) return;
try {
const response = await $fetch('/api/comments', {
method: 'POST',
body: {
content: commentText,
articleId,
likes: 0,
dislikes: 0,
},
});
if (response.success) {
const article = articles.value.find((a) => a.id === articleId);
if (article) {
article.comments.push({
id: response.data.id,
content: commentText,
likes: 0,
dislikes: 0,
});
}
// Reset the input field for the current article
newComments.value[articleId] = '';
// Display success overlay
showOverlay(
{
icon: 'mdi-check-circle',
message: `Merci pour votre suggestion. Veuillez la consulter tout en descendant vers le bas de la page.`,
buttonText: 'Continuer',
iconColor: 'success',
},
overlay
);
console.log('Comment added successfully');
} else {
console.error('Failed to add comment:', response.message);
}
} catch (error) {
console.error('Error in addComment:', error);
}
};
const submitSignature = async () => {
if (isFormValid.value) {
const signatureData = {
name: name.value,
email: email.value,
comment: comment.value,
};
signatures.value.push({ ...signatureData });
try {
await $fetch('/api/charte', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(signatureData)
})
// Display success overlay using the reusable function
showOverlay(
{
icon: 'mdi-check-circle',
message: `Merci pour votre signature ${name.value}`,
buttonText: 'Continuer',
iconColor: 'success',
},
overlay
)
name.value = '';
email.value = '';
comment.value = '';
console.log('Charte is submitted successfully');
} catch (error) {
console.error('Error in submit signature:', error);
}
} return;
};
</script>
<style scoped>
.video-section {
position: relative;
width: 100%;
height: 50vh;
overflow: hidden;
margin-bottom: 20px;
}
.video-background {
width: 100%;
height: 100%;
object-fit: cover;
}
h1 {
font-weight: bold;
font-family: "Times New Roman", Times, serif;
}
p {
font-family: "Times New Roman", Times, serif;
}
.v-expansion-panel-title {
font-family: Georgia;
}
.v-card {
font-family: Georgia;
}
.overlay-msg {
font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
}
.custom-btn {
font-size: 12px;
font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
padding: 4px 8px;
min-height: 24px;
min-width: 40px;
line-height: 1.2;
}
.v-icon:hover {
opacity: 0.5;
}
</style>