front_foundation #5
10
front/MyINPulse-front/fake_data/db.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"entrepreneurs": [
|
||||
{ "id": 1, "name": "Alice", "email": "alice@example.com" },
|
||||
{ "id": 2, "name": "Bob", "email": "bob@example.com" },
|
||||
{ "id": 3, "name": "Charlie", "email": "charlie@example.com" }
|
||||
],
|
||||
"data": [
|
||||
{ "canva_data": "this is a fake data to test api" }
|
||||
]
|
||||
}
|
2
front/MyINPulse-front/fake_data/open.sh
Executable file
@ -0,0 +1,2 @@
|
||||
#!/usr/bin/bash
|
||||
json-server --watch db.json --port 5000
|
181
front/MyINPulse-front/src/components/canvas/CanvasItem.vue
Normal file
@ -0,0 +1,181 @@
|
||||
<template>
|
||||
|
||||
<div :class="['cell', { expanded }]" @click="handleClick">
|
||||
<h3>{{ title }}</h3>
|
||||
|
||||
<!-- Mode affichage -->
|
||||
<template v-if="!isEditing">
|
||||
<p>{{ currentDescription }}</p>
|
||||
<button v-if="expanded" @click.stop="startEditing" class="edit-button">Éditer</button>
|
||||
</template>
|
||||
|
||||
<!-- Mode édition -->
|
||||
<template v-else>
|
||||
mohamed_maoulainine marked this conversation as resolved
Outdated
piair
commented
ce n'est pas de la composition API ce n'est pas de la composition API
|
||||
<textarea v-model="editedDescription" class="edit-input"></textarea>
|
||||
<div class="button-container">
|
||||
<button @click.stop="saveEdit" class="save-button">Enregistrer</button>
|
||||
<button @click.stop="cancelEdit" class="cancel-button">Annuler</button>
|
||||
mohamed_maoulainine marked this conversation as resolved
Outdated
piair
commented
composition API composition API
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, defineProps } from "vue";
|
||||
import axios from "axios";
|
||||
|
||||
const props = defineProps<{
|
||||
title: string;
|
||||
description: string;
|
||||
}>();
|
||||
|
||||
const expanded = ref(false);
|
||||
const isEditing = ref(false);
|
||||
const currentDescription = ref(props.description);
|
||||
const editedDescription = ref(props.description);
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const response = await axios.get("http://localhost:5000/data"); // Met à jour l'URL
|
||||
if (response.data.length > 0) {
|
||||
currentDescription.value = response.data[0].canva_data;
|
||||
editedDescription.value = response.data[0].canva_data;
|
||||
} else {
|
||||
console.warn("Aucune donnée reçue.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la récupération des données :", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClick = async () => {
|
||||
if (!expanded.value) {
|
||||
await fetchData();
|
||||
} else if (!isEditing.value) {
|
||||
currentDescription.value = props.description;
|
||||
editedDescription.value = props.description;
|
||||
}
|
||||
|
||||
if (!isEditing.value) {
|
||||
expanded.value = !expanded.value;
|
||||
}
|
||||
};
|
||||
|
||||
const startEditing = () => {
|
||||
isEditing.value = true;
|
||||
};
|
||||
|
||||
const saveEdit = () => {
|
||||
currentDescription.value = editedDescription.value;
|
||||
isEditing.value = false;
|
||||
};
|
||||
|
||||
const cancelEdit = () => {
|
||||
editedDescription.value = currentDescription.value;
|
||||
isEditing.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import "@/components/canvas/style-project.css";
|
||||
|
||||
.cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.expanded-content {
|
||||
justify-content: flex-start !important;
|
||||
}
|
||||
|
||||
.cell:not(.expanded):hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 8px 9px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.cell h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
/*margin-bottom: 10px;*/
|
||||
}
|
||||
|
||||
.cell p {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.expanded {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: white;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.edit-input {
|
||||
width: 80%;
|
||||
height: 100px;
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.edit-button, .save-button, .cancel-button {
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s ease;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.edit-button {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.edit-button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.save-button:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.cancel-button:hover {
|
||||
background-color: #c82333;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
120
front/MyINPulse-front/src/components/canvas/HeaderCanvas.vue
Normal file
@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<header>
|
||||
<img src="../icons/logo inpulse.png" alt="INPulse Logo">
|
||||
<div class="header-buttons">
|
||||
<div class="menu">
|
||||
<button class="contact-button" @click="toggleDropdown">Contact</button>
|
||||
<div class="contact-dropdown" v-bind:class="{ 'dropdown-visible': isDropdownOpen }">
|
||||
<button @click="contactAll">Contact All</button>
|
||||
<button v-for="(email, index) in entrepreneurEmails" :key="index">
|
||||
{{ email }}
|
||||
</button>
|
||||
</div>
|
||||
<button class="return-button">
|
||||
mohamed_maoulainine marked this conversation as resolved
Outdated
piair
commented
plutôt utiliser des routerLink au lieu de href plutôt utiliser des routerLink au lieu de href
https://stackoverflow.com/questions/52675885/when-to-use-router-link-vs-a
|
||||
<RouterLink to="/">Return to list project</RouterLink>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
mohamed_maoulainine marked this conversation as resolved
Outdated
piair
commented
Ce n'est pas de la composition API, a changer Ce n'est pas de la composition API, a changer
|
||||
|
||||
piair
commented
Il faut se servir de Il faut se servir de `axiosInstance`, voir https://gitea.piair.dev/piair/MyINPulse/src/branch/main/front/MyINPulse-front/src/services/api.ts.
Cela permet de préconfigurer l'authentification et le host du backend.
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import axios from "axios";
|
||||
|
||||
const isDropdownOpen = ref(false);
|
||||
const entrepreneurEmails = ref([]);
|
||||
mohamed_maoulainine marked this conversation as resolved
Outdated
piair
commented
Vérifier que le type de données renvoyé par le backend correspond bien à ca (ce n'est pas le cas). Vérifier que le type de données renvoyé par le backend correspond bien à ca (ce n'est pas le cas).
|
||||
|
||||
const toggleDropdown = () => {
|
||||
isDropdownOpen.value = !isDropdownOpen.value;
|
||||
console.log("Dropdown toggled:", isDropdownOpen.value); // for debug purposes
|
||||
};
|
||||
|
||||
const fetchEntrepreneurs = async () => {
|
||||
try {
|
||||
const response = await axios.get("http://localhost:5000/entrepreneurs");
|
||||
entrepreneurEmails.value = response.data.map(e => e.email);
|
||||
mohamed_maoulainine marked this conversation as resolved
Outdated
piair
commented
il faut pas Hardcoder une addresse, elle est dans le fichier .env. Dans tout les cas, cette adresse ne devrait pas être là. il faut pas Hardcoder une addresse, elle est dans le fichier .env. Dans tout les cas, cette adresse ne devrait pas être là.
piair
commented
je viens de voir le message de commit, c'est en effet logique d'utiliser cette donnée. Par contre je suis pas sur que ce soit la bonne méthode pour faire des tests, c'est a discuter. Il serait bien d'appeler mock-data au lieu de fake-data je viens de voir le message de commit, c'est en effet logique d'utiliser cette donnée. Par contre je suis pas sur que ce soit la bonne méthode pour faire des tests, c'est a discuter.
Il serait bien d'appeler mock-data au lieu de fake-data
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la récupération des entrepreneurs:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const contactAll = () => {
|
||||
alert("Contacter tous les entrepreneurs : " + entrepreneurEmails.value.join(", "));
|
||||
};
|
||||
|
||||
onMounted(fetchEntrepreneurs);
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
@import "@/components/canvas/style-project.css";
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.contact-button, .return-button {
|
||||
background-color: #000;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.return-button a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.contact-dropdown {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
margin-top: 5px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.contact-dropdown button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
text-align: left;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.contact-dropdown button:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.dropdown-visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
header img {
|
||||
width: 100px;
|
||||
height: auto;
|
||||
}
|
||||
</style>
|
56
front/MyINPulse-front/src/components/canvas/LeanCanvas.vue
Normal file
@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="canvas">
|
||||
<CanvasItem
|
||||
v-for="(item, index) in items"
|
||||
:key="index"
|
||||
:title="item.title"
|
||||
:description="item.description"
|
||||
:class="item.class"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
mohamed_maoulainine marked this conversation as resolved
Outdated
piair
commented
Ce n'est pas de la composition API Ce n'est pas de la composition API
|
||||
import { ref } from "vue";
|
||||
import CanvasItem from "@/components/canvas/CanvasItem.vue";
|
||||
|
||||
const items = ref([
|
||||
{ title: "1. Problème", description: "3 problèmes essentiels à résoudre pour le client", class: "Probleme" },
|
||||
{ title: "2. Segments", description: "Les segments de clientèle visés", class: "Segments" },
|
||||
{ title: "3. Valeur", description: "La proposition de valeur", class: "Valeur" },
|
||||
{ title: "4. Solution", description: "Les solutions proposées", class: "Solution" },
|
||||
{ title: "5. Avantage", description: "Les avantages concurrentiels", class: "Avantage" },
|
||||
{ title: "6. Canaux", description: "Les canaux de distribution", class: "Canaux" },
|
||||
{ title: "7. Indicateurs", description: "Les indicateurs clés de performance", class: "Indicateurs" },
|
||||
{ title: "8. Coûts", description: "Les coûts associés", class: "Couts" },
|
||||
{ title: "9. Revenus", description: "Les sources de revenus", class: "Revenus" }
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import "@/components/canvas/style-project.css";
|
||||
|
||||
.canvas {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(10, 1fr);
|
||||
grid-template-rows: repeat(6, 1fr);
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
max-width: 1200px;
|
||||
margin: 20px auto;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
height: 80vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.Probleme { grid-column: 1 / 3; grid-row: 1 / 5; }
|
||||
.Segments { grid-column: 9 / 11; grid-row: 1 / 5; }
|
||||
.Valeur { grid-column: 5 / 7; grid-row: 1 / 5; }
|
||||
.Solution { grid-column: 3 / 5; grid-row: 1 / 3; }
|
||||
.Avantage { grid-column: 7 / 9; grid-row: 1 / 3; }
|
||||
.Canaux { grid-column: 7 / 9; grid-row: 3 / 5; }
|
||||
.Indicateurs { grid-column: 3 / 5; grid-row: 3 / 5; }
|
||||
.Couts { grid-column: 1 / 6; grid-row: 5 / 7; }
|
||||
.Revenus { grid-column: 6 / 11; grid-row: 5 / 7; }
|
||||
</style>
|
156
front/MyINPulse-front/src/components/canvas/style-project.css
Normal file
@ -0,0 +1,156 @@
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.cell {
|
||||
flex: 1;
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.produit {
|
||||
background-color: #f9e4e4;
|
||||
mohamed_maoulainine marked this conversation as resolved
piair
commented
ce serait cool d'utiliser des variables comme ça https://stackoverflow.com/questions/1875852/how-can-i-define-colors-as-variables-in-css , mais c'est aps urgent du tout ce serait cool d'utiliser des variables comme ça https://stackoverflow.com/questions/1875852/how-can-i-define-colors-as-variables-in-css , mais c'est aps urgent du tout
|
||||
}
|
||||
|
||||
.marche {
|
||||
background-color: #e4f1f9;
|
||||
}
|
||||
|
||||
.valeur {
|
||||
background-color: #f9f4e4;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 5px 0 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
h1 img {
|
||||
height: 80px;
|
||||
margin: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#ade {
|
||||
max-width: 1200px;
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
background-color: #e8f5e9;
|
||||
border: 2px solid #4caf50;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#ade h3 {
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
#ade p {
|
||||
margin: 10px 0;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 20px;
|
||||
background-color: #fff;
|
||||
border-bottom: 2px solid #ddd;
|
||||
}
|
||||
|
||||
header img {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
header .contact-menu {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.contact-button, .return {
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background-color: #2196f3;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.contact-button:hover, .return:hover {
|
||||
background-color: #1976d2;
|
||||
}
|
||||
|
||||
/* Dropdown styling */
|
||||
.contact-dropdown {
|
||||
piair
commented
Si cela ne concerne que le dropdown d'un contact, cela devrait être dans le style de la view contact Si cela ne concerne que le dropdown d'un contact, cela devrait être dans le style de la view contact
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50px;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 15px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.contact-dropdown button {
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background-color: #4caf50;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.contact-dropdown button:hover {
|
||||
background-color: #388e3c;
|
||||
}
|
||||
|
||||
.return {
|
||||
background-color: #f44336;
|
||||
}
|
||||
|
||||
.return:hover {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
|
||||
a{
|
||||
color: white;
|
||||
}
|
14
front/MyINPulse-front/src/plugins/authStore.ts
Normal file
@ -0,0 +1,14 @@
|
||||
// file: src/plugins/authStore.js
|
||||
|
||||
import { useAuthStore } from "@/stores/authStore.ts";
|
||||
import keycloakService from '@/services/keycloak';
|
||||
// Setup auth store as a plugin so it can be accessed globally in our FE
|
||||
const authStorePlugin = {
|
||||
install(app: any, option: any) {
|
||||
const store = useAuthStore(option.pinia);
|
||||
app.config.globalProperties.$store = store;
|
||||
keycloakService.CallInitStore(store);
|
||||
}
|
||||
}
|
||||
|
||||
export default authStorePlugin;
|
Il serait mieux d'afficher le contenu du lean canvas sans devoir clicker dessus, un peu comme sur ce lien.
De plus, ce n'est pas important d'afficher en gros le titre de la section, il devrait être plus discret.
https://cms.boardmix.com/images/articles/lean-canvas.png
En plus il y a des jolies couleur c'est sympa.