feat: using bootstrap..
Some checks failed
Format / formatting (push) Successful in 7s
Build / build (push) Successful in 43s
CI / build (push) Failing after 11s
Format / formatting (pull_request) Successful in 6s

This commit is contained in:
ALAMI Adnane 2025-04-11 19:42:38 +02:00
parent 60290956ec
commit f8991e90ab
5 changed files with 209 additions and 42 deletions

View File

@ -1,6 +1,6 @@
<template> <template>
<div :class="['cell', { expanded }]" @click="handleClick"> <div :class="['cell', { expanded }]" @click="handleClick">
<h3>{{ title_text }}</h3> <h3 class="fs-5 fw-medium">{{ title_text }}</h3>
<div class="section-bloc" v-for="(desc, index) in currentDescriptions" :key="index"> <div class="section-bloc" v-for="(desc, index) in currentDescriptions" :key="index">
@ -8,7 +8,7 @@
<template v-if="IS_ADMIN"> <template v-if="IS_ADMIN">
<div class="description"> <div class="description">
<p>{{ desc }}</p> <p class="m-0">{{ desc }}</p>
</div> </div>
</template> </template>
@ -18,7 +18,7 @@
<!-- Mode affichage --> <!-- Mode affichage -->
<template v-if="!isEditing[index]"> <template v-if="!isEditing[index]">
<div class="description"> <div class="description">
<p>{{ desc }}</p> <p class="m-0">{{ desc }}</p>
</div> </div>
<div class="button-container"> <div class="button-container">
<button v-if="expanded" @click.stop="startEditing(index)" class="edit-button">Éditer</button> <button v-if="expanded" @click.stop="startEditing(index)" class="edit-button">Éditer</button>
@ -45,8 +45,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, defineProps } from "vue"; import { ref, defineProps, onMounted } from "vue";
import axios from "axios"; import axios from "axios";
import { axiosInstance } from "@/services/api.ts";
const IS_MOCK_MODE = true; const IS_MOCK_MODE = true;
const IS_ADMIN = false; const IS_ADMIN = false;
@ -64,8 +65,12 @@ currentDescriptions.value[0] = props.description;
const editedDescriptions = ref<string[]>([]); const editedDescriptions = ref<string[]>([]);
const isEditing = ref<boolean[]>([]); const isEditing = ref<boolean[]>([]);
onMounted(() => {
fetchData(props.projectId, props.title, "NaN", IS_MOCK_MODE);
});
/*
/* FOR LOCAL DATABASE
const fetchData = async () => { const fetchData = async () => {
try { try {
const response = await axios.get("http://localhost:5000/data"); // Met à jour l'URL const response = await axios.get("http://localhost:5000/data"); // Met à jour l'URL
@ -82,6 +87,7 @@ const fetchData = async () => {
*/ */
// Fonction fetchData avec possibilité d'utiliser le mock // Fonction fetchData avec possibilité d'utiliser le mock
/* FOR FETCHING WITH AXIOS DIRECTLY
const fetchData = async (projectId: number, title: number, date: string, useMock = false) => { const fetchData = async (projectId: number, title: number, date: string, useMock = false) => {
try { try {
const responseData = useMock const responseData = useMock
@ -89,6 +95,27 @@ const fetchData = async (projectId: number, title: number, date: string, useMock
: (await axios.get<{ txt: string }[]>( : (await axios.get<{ txt: string }[]>(
`http://localhost:5000/shared/projects/lcsection/${projectId}/${title}/${date}` `http://localhost:5000/shared/projects/lcsection/${projectId}/${title}/${date}`
)).data; )).data;
if (responseData.length > 0) {
currentDescriptions.value = responseData.map((item) => item.txt);
editedDescriptions.value = [...currentDescriptions.value];
isEditing.value = Array(responseData.length).fill(false);
} else {
console.warn("Aucune donnée reçue.");
}
} catch (error) {
console.error("Erreur lors de la récupération des données :", error);
}
};
*/
// Fonction fetchData avec possibilité d'utiliser le mock
const fetchData = async (projectId: number, title: number, date: string, useMock = false) => {
try {
const responseData = useMock
? await mockFetch(projectId, title, date)
: (await axiosInstance.get<{ txt: string }[]>(
`/shared/projects/lcsection/${projectId}/${title}/${date}`
)).data;
if (responseData.length > 0) { if (responseData.length > 0) {
currentDescriptions.value = responseData.map((item) => item.txt); currentDescriptions.value = responseData.map((item) => item.txt);
@ -102,7 +129,6 @@ const fetchData = async (projectId: number, title: number, date: string, useMock
} }
}; };
// Fonction de simulation de l'API // Fonction de simulation de l'API
const mockFetch = async (projectId: number, title: number, date: string) => { const mockFetch = async (projectId: number, title: number, date: string) => {
console.log(`Mock fetch pour projectId: ${projectId}, title: ${title}, date: ${date}`); console.log(`Mock fetch pour projectId: ${projectId}, title: ${title}, date: ${date}`);
@ -110,9 +136,9 @@ const mockFetch = async (projectId: number, title: number, date: string) => {
return new Promise<{ txt: string }[]>((resolve) => { return new Promise<{ txt: string }[]>((resolve) => {
setTimeout(() => { setTimeout(() => {
resolve([ resolve([
{txt: "Ceci est une description 1 pour tester le front. Franchement, si tu es encore en train de lire, cest probablement que tu veux t'assurer que tout saffiche bien même avec un texte bien long. Allez, encore quelques mots inutiles pour bien remplir la ligne et tester les retours à la ligne. Bravo champion."}, {txt: "Ceci est une description 1 pour tester le front."},
{txt: "Deuxième description : elle est là pour taider à tester le comportement du composant avec du texte plus dense. Peut-être même avec quelques caractères spéciaux, tiens : éèàçù$#@! Et si on faisait semblant davoir du contenu sérieux ? Non, toujours pas."}, {txt: "Deuxième description."},
{txt: "Troisième description, probablement inutile mais bon, on continue pour la forme. À ce stade, tu testes sûrement si le champ de texte peut supporter un pavé sans exploser. Spoiler alert : si t'as tout bien codé, ça devrait aller. Sinon, bah... débogue. Courage."} {txt: "Troisième description."}
]); ]);
}, 500); // Simule un délai réseau de 500ms }, 500); // Simule un délai réseau de 500ms
}); });
@ -199,6 +225,7 @@ const cancelEdit = (index: number) => {
<style scoped> <style scoped>
@import "@/components/canvas/style-project.css"; @import "@/components/canvas/style-project.css";
.cell { .cell {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -210,6 +237,7 @@ const cancelEdit = (index: number) => {
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 5px rgba(0, 0, 0, 0.1);
} }
.expanded-content { .expanded-content {
justify-content: flex-start !important; justify-content: flex-start !important;
} }
@ -220,13 +248,13 @@ const cancelEdit = (index: number) => {
} }
.cell h3 { .cell h3 {
font-size: 25px; font-size: 15px;
font-weight: 500; font-weight: 500;
font-family: 'Arial', sans-serif; font-family: 'Arial', sans-serif;
} }
.p { .p {
font-size: 25px; font-size: 10px;
color: #666; color: #666;
font-family: 'Arial', sans-serif; font-family: 'Arial', sans-serif;
} }
@ -266,6 +294,8 @@ const cancelEdit = (index: number) => {
text-align: center; text-align: center;
} }
.edit-input { .edit-input {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -287,6 +317,7 @@ const cancelEdit = (index: number) => {
padding-right: 1%; padding-right: 1%;
} }
.section-bloc ,.editing-section-bloc { .section-bloc ,.editing-section-bloc {
width: 100%; width: 100%;
justify-content: center; justify-content: center;
@ -296,6 +327,10 @@ const cancelEdit = (index: number) => {
margin: 10px; margin: 10px;
} }
.edit-button { .edit-button {
width: 100px; width: 100px;
height: 40px; height: 40px;

View File

@ -7,9 +7,14 @@
<button class="contact-button" @click="toggleDropdown">Contact</button> <button class="contact-button" @click="toggleDropdown">Contact</button>
<div class="contact-dropdown" :class="{ 'dropdown-visible': isDropdownOpen }"> <div class="contact-dropdown" :class="{ 'dropdown-visible': isDropdownOpen }">
<button @click="contactAll">Contacter tous</button> <button @click="contactAll">Contacter tous</button>
<button v-for="(email, index) in entrepreneurEmails" :key="index"> <button
v-for="(email, index) in entrepreneurEmails"
:key="index"
@click="copyToClipboard(email)"
>
{{ email }} {{ email }}
</button> </button>
</div> </div>
</div> </div>
@ -23,35 +28,40 @@
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import axios from "axios"; import axios from "axios";
const IS_MOCK_MODE = true; const IS_MOCK_MODE = true;
const props = defineProps<{ const props = defineProps<{
projectId: number; projectId: number;
}>(); }>();
type Entrepreneur = { type Entrepreneur = {
id: number; idUser: number;
name: string; userSurname: string;
userName: string;
primaryMail: string; primaryMail: string;
secondaryMail: string;
phoneNumber: string;
school: string;
course: string;
sneeStatus: boolean;
}; };
const isDropdownOpen = ref(false); const isDropdownOpen = ref(false);
const entrepreneurEmails = ref([]); const entrepreneurEmails = ref<string[]>([]);
const toggleDropdown = () => { const toggleDropdown = () => {
isDropdownOpen.value = !isDropdownOpen.value; isDropdownOpen.value = !isDropdownOpen.value;
console.log("Dropdown toggled:", isDropdownOpen.value); console.log("Dropdown toggled:", isDropdownOpen.value);
}; };
const fetchEntrepreneurs = async (projectId: number, useMock = IS_MOCK_MODE) => {
const fetchEntrepreneurs = async (projectId :number, useMock = IS_MOCK_MODE) => {
try { try {
const responseData = useMock const responseData: Entrepreneur[] = useMock
? await mockFetchEntrepreneurs(projectId) ? await mockFetchEntrepreneurs(projectId)
: (await axios.get(`http://localhost:5000/shared/projects/entrepreneurs/${projectId}`)).data; : (await axios.get(`http://localhost:5000/shared/projects/entrepreneurs/${projectId}`)).data;
if (responseData.length > 0) { if (responseData.length > 0) {
entrepreneurEmails.value = responseData.map((item) => item.primaryMail); entrepreneurEmails.value = responseData.map((item: Entrepreneur) => item.primaryMail);
} else { } else {
console.warn("Aucun entrepreneur trouvé."); console.warn("Aucun entrepreneur trouvé.");
} }
@ -98,6 +108,14 @@ const contactAll = () => {
alert("Contacter tous les entrepreneurs : " + entrepreneurEmails.value.join(", ")); alert("Contacter tous les entrepreneurs : " + entrepreneurEmails.value.join(", "));
}; };
const copyToClipboard = (email: string) => {
navigator.clipboard.writeText(email).then(() => {
alert(`Adresse copiée : ${email}`);
}).catch(err => {
console.error("Erreur lors de la copie :", err);
});
};
onMounted(() => fetchEntrepreneurs(props.projectId, IS_MOCK_MODE)); onMounted(() => fetchEntrepreneurs(props.projectId, IS_MOCK_MODE));
</script> </script>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="canvas"> <div class="canvas container-fluid">
<CanvasItem <CanvasItem
v-for="(item, index) in items" v-for="(item, index) in items"
:key="index" :key="index"
@ -7,13 +7,13 @@
:title_text="item.title_text" :title_text="item.title_text"
:description="item.description" :description="item.description"
:projectId="item.projectId" :projectId="item.projectId"
:class="item.class" :class="['canvas-item', item.class, 'card', 'shadow', 'p-3']"
/> />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref, onMounted } from "vue";
import CanvasItem from "@/components/canvas/CanvasItem.vue"; import CanvasItem from "@/components/canvas/CanvasItem.vue";
const items = ref([ const items = ref([
@ -28,6 +28,20 @@ const items = ref([
{ projectId: 1, title: 9, title_text: "9. Revenus", description: "Les sources de revenus", class: "Revenus" } { projectId: 1, title: 9, title_text: "9. Revenus", description: "Les sources de revenus", class: "Revenus" }
]); ]);
onMounted(() => {
const bootstrapCss = document.createElement('link')
bootstrapCss.rel = 'stylesheet'
bootstrapCss.href = 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css'
bootstrapCss.integrity = 'sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+Fpc+NC'
bootstrapCss.crossOrigin = 'anonymous'
document.head.appendChild(bootstrapCss)
const bootstrapJs = document.createElement('script')
bootstrapJs.src = 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js'
bootstrapJs.integrity = 'sha384-mQ93S0EhrF4Z1nM+fTflmYf0DyzsY5j7F5H3WlClDD6H3WUJh6kxBkF3GDW8n1j6'
bootstrapJs.crossOrigin = 'anonymous'
document.body.appendChild(bootstrapJs)
})
</script> </script>
<style scoped> <style scoped>
@ -37,23 +51,27 @@ const items = ref([
display: grid; display: grid;
grid-template-columns: repeat(10, 1fr); grid-template-columns: repeat(10, 1fr);
grid-template-rows: repeat(6, 1fr); grid-template-rows: repeat(6, 1fr);
gap: 10px; gap: 12px;
padding: 10px; padding: 30px;
max-width: 1200px; /*background-color: #f8f9fa;*/
margin: 20px auto;
background-color: #fff;
position: relative; position: relative;
height: 80vh; height: 90vh;
overflow: auto; overflow: auto;
} }
.Probleme { grid-column: 1 / 3; grid-row: 1 / 5; padding-top: 20px;} .Probleme { grid-column: 1 / 3; grid-row: 1 / 5; }
.Segments { grid-column: 9 / 11; grid-row: 1 / 5; padding-top: 20px;} .Segments { grid-column: 9 / 11; grid-row: 1 / 5; }
.Valeur { grid-column: 5 / 7; grid-row: 1 / 5; padding-top: 20px;} .Valeur { grid-column: 5 / 7; grid-row: 1 / 5; }
.Solution { grid-column: 3 / 5; grid-row: 1 / 3; padding-top: 20px;} .Solution { grid-column: 3 / 5; grid-row: 1 / 3; }
.Avantage { grid-column: 7 / 9; grid-row: 1 / 3; padding-top: 20px;} .Avantage { grid-column: 7 / 9; grid-row: 1 / 3; }
.Canaux { grid-column: 7 / 9; grid-row: 3 / 5; padding-top: 20px;} .Canaux { grid-column: 7 / 9; grid-row: 3 / 5; }
.Indicateurs { grid-column: 3 / 5; grid-row: 3 / 5; padding-top: 20px;} .Indicateurs { grid-column: 3 / 5; grid-row: 3 / 5; }
.Couts { grid-column: 1 / 6; grid-row: 5 / 7; padding-top: 20px;} .Couts { grid-column: 1 / 6; grid-row: 5 / 7; }
.Revenus { grid-column: 6 / 11; grid-row: 5 / 7; padding-top: 20px;} .Revenus { grid-column: 6 / 11; grid-row: 5 / 7; }
.canvas-item {
/*background-color: white;*/
border: 1px solid #dee2e6;
border-radius: 0.5rem;
}
</style> </style>

View File

@ -89,4 +89,4 @@ function deleteApi(
} }
export { callApi, postApi, deleteApi }; export { axiosInstance, callApi, postApi, deleteApi };

View File

@ -1,12 +1,24 @@
<template> <template>
<div> <div>
<header> <header>
<HeaderCanvas /> <HeaderCanvas :projectId="1" />
</header> </header>
</div> </div>
<div> <div>
<h1>Page Canvas</h1> <h1 class="page-title">PAGE CANVAS</h1>
<p class="canvas-help-text">
Cliquez sur un champ du tableau pour afficher son contenu en détail ci-dessous.
</p>
<LeanCanvas /> <LeanCanvas />
<div class="info-box">
<p>
Responsable : <strong>{{ admin.userName }} {{ admin.userSurname }}</strong><br />
Contact : <a href="mailto:{{ admin.primaryMail }}">{{ admin.primaryMail }}</a> |
<a href="tel:{{ admin.phoneNumber }}">{{ admin.phoneNumber }}</a>
</p>
</div>
</div> </div>
</template> </template>
@ -14,5 +26,89 @@
// @ts-ignore // @ts-ignore
import HeaderCanvas from "../components/canvas/HeaderCanvas.vue"; import HeaderCanvas from "../components/canvas/HeaderCanvas.vue";
import LeanCanvas from '../components/canvas/LeanCanvas.vue'; import LeanCanvas from '../components/canvas/LeanCanvas.vue';
import { ref, onMounted } from "vue";
import { axiosInstance } from "@/services/api.ts";
const IS_MOCK_MODE = true;
// Variables pour les informations de l'administrateur
const admin = ref({
idUser: 0,
userSurname: "",
userName: "",
primaryMail: "",
secondaryMail: "",
phoneNumber: ""
});
const mockAdminData = {
idUser: 1,
userSurname: "ALAMI",
userName: "Adnane",
primaryMail: "mock.admin@example.com",
secondaryMail: "admin.backup@example.com",
phoneNumber: "0600000000"
};
// Fonction pour récupérer les données de l'administrateur
const fetchAdminData = async (projectId: number, useMock = IS_MOCK_MODE) => {
try {
if (useMock) {
console.log("Utilisation des données mockées pour l'administrateur");
admin.value = mockAdminData;
return;
}
const response = await axiosInstance.get(`/shared/projects/admin/${projectId}`);
admin.value = response.data;
} catch (error) {
console.error("Erreur lors de la récupération des données de l'administrateur :", error);
}
};
// Appeler la fonction fetch au montage du composant
onMounted(() => {
const projectId = 1;
fetchAdminData(projectId);
});
</script> </script>
<style scoped>
.page-title {
text-align: center;
font-size: 2.5rem;
margin-top: 20px;
}
.canvas-help-text {
text-align: center;
font-size: 0.7rem;
color: #666;
}
.info-box {
background-color: #f9f9f9;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
font-family: Arial, sans-serif;
width: 30%;
max-width: 600px;
margin: 20px auto;
}
.info-box p {
font-size: 16px;
line-height: 1.5;
color: #333;
}
.info-box a {
color: #007bff;
text-decoration: none;
}
.info-box a:hover {
text-decoration: underline;
}
</style>