front_foundation #9

Closed
mohamed_maoulainine wants to merge 181 commits from front_foundation into main
17 changed files with 1414 additions and 1286 deletions
Showing only changes of commit c60fb8945b - Show all commits

View File

@ -1,16 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { /*RouterLink,*/ RouterView } from 'vue-router' import { /*RouterLink,*/ RouterView } from "vue-router";
Review

I don't like the comments in the middle of the line, it would be better to delete this part.

I don't like the comments in the middle of the line, it would be better to delete this part.
import ErrorWrapper from "@/views/errorWrapper.vue"; import ErrorWrapper from "@/views/errorWrapper.vue";
</script> </script>
<template> <template>
<Header />
<Header />
<ErrorWrapper /> <ErrorWrapper />
Review

same, I don't think it's really important to keep comments

same, I don't think it's really important to keep comments
<!--<RouterLink to="/">Home</RouterLink> | --> <!--<RouterLink to="/">Home</RouterLink> | -->
<!--<RouterLink to="/canvas">Canvas</RouterLink> --> <!--<RouterLink to="/canvas">Canvas</RouterLink> -->
<RouterView /> <RouterView />
</template> </template>

View File

@ -1,110 +1,108 @@
<template> <template>
Review

We must keep the same order for template and script everywhere.

We must keep the same order for template and script everywhere.
<form class="add-project-form" @submit.prevent="submitProject"> <form class="add-project-form" @submit.prevent="submitProject">
<h2>Ajouter un projet</h2> <h2>Ajouter un projet</h2>
<div class="form-group"> <div class="form-group">
<label for="projectName">Nom du projet</label> <label for="projectName">Nom du projet</label>
<input <input
id="projectName" id="projectName"
v-model="project.projectName" v-model="project.projectName"
type="text" type="text"
required required
/> />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="creationDate">Date de création</label> <label for="creationDate">Date de création</label>
<input <input
id="creationDate" id="creationDate"
v-model="project.creationDate" v-model="project.creationDate"
type="text" type="text"
placeholder="JJ-MM-AAAA" placeholder="JJ-MM-AAAA"
required required
/> />
</div> </div>
<div class="form-group"> <div class="form-group">
Outdated
Review

For now, it would be better to hide this altogether, I don't think we will have time to implement it.

For now, it would be better to hide this altogether, I don't think we will have time to implement it.
<label for="logo">Logo</label> <label for="logo">Logo</label>
<input <input
id="logo" id="logo"
v-model="project.logo" v-model="project.logo"
type="text" type="text"
placeholder="(à discuter)" placeholder="(à discuter)"
/> />
</div> </div>
<button type="submit">Ajouter</button> <button type="submit">Ajouter</button>
</form> </form>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
import { postApi } from "@/services/api.ts"; import { postApi } from "@/services/api.ts";
const project = ref({ const project = ref({
projectName: "", projectName: "",
creationDate: "", creationDate: "",
logo: "to be discussed not yet fixed", logo: "to be discussed not yet fixed",
}); });
function submitProject() {
postApi("/admin/projects/add", project.value);
}
</script>
<style scoped>
h2{ function submitProject() {
postApi("/admin/projects/add", project.value);
}
</script>
<style scoped>
h2 {
Outdated
Review

The h2 from this file is the same as the h3 from the next (AgendaComponent). It would be better to have a single css file for this part.

The h2 from this file is the same as the h3 from the next (AgendaComponent). It would be better to have a single css file for this part.
font-size: 1.5rem; font-size: 1.5rem;
color: #333; color: #333;
margin-bottom: 1.2rem; margin-bottom: 1.2rem;
border-bottom: 2px solid #ddd; border-bottom: 2px solid #ddd;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
} }
.add-project-form { .add-project-form {
max-width: 500px; max-width: 500px;
margin: 0 auto; margin: 0 auto;
padding: 20px; padding: 20px;
background: #fff; background: #fff;
border-radius: 10px; border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
} }
h2 { h2 {
margin-bottom: 20px; margin-bottom: 20px;
font-size: 24px; font-size: 24px;
color: #333; color: #333;
} }
.form-group { .form-group {
margin-bottom: 15px; margin-bottom: 15px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
label { label {
font-weight: bold; font-weight: bold;
margin-bottom: 5px; margin-bottom: 5px;
} }
input { input {
padding: 8px; padding: 8px;
border-radius: 5px; border-radius: 5px;
border: 1px solid #ccc; border: 1px solid #ccc;
} }
Review

same, button should look the same in the project, so the css shouldn't be scoped here

same, button should look the same in the project, so the css shouldn't be scoped here
button { button {
background-color: #4caf50; background-color: #4caf50;
color: white; color: white;
padding: 10px 15px; padding: 10px 15px;
border: none; border: none;
border-radius: 5px; border-radius: 5px;
cursor: pointer; cursor: pointer;
} }
button:hover { button:hover {
background-color: #45a049; background-color: #45a049;
} }
</style> </style>

View File

@ -2,98 +2,92 @@
<div id="agenda"> <div id="agenda">
<h3>Rendez-vous</h3> <h3>Rendez-vous</h3>
<table> <table>
<thead> <thead>
<tr> <tr>
<th>Projet</th> <th>Projet</th>
<th>Date</th> <th>Date</th>
<th>Lieu</th> <th>Lieu</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(p, index) in projectRDV" :key="index"> <tr v-for="(p, index) in projectRDV" :key="index">
<td>{{ p.projectName }}</td> <td>{{ p.projectName }}</td>
<td>{{ p.date }}</td> <td>{{ p.date }}</td>
<td>{{ p.lieu }}</td> <td>{{ p.lieu }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineProps } from "vue"; import { defineProps } from "vue";
interface rendezVous{ interface rendezVous {
projectName: string, projectName: string;
date: string, date: string;
lieu: string, lieu: string;
} }
defineProps<{ defineProps<{
projectRDV: rendezVous[] projectRDV: rendezVous[];
Review

où est-ce que les RDV sont récupérés ?

où est-ce que les RDV sont récupérés ?
}>(); }>();
</script> </script>
<style scoped> <style scoped>
h3 {
Review

same comment as before, most of the style here is not for this part only, so it should not be here

same comment as before, most of the style here is not for this part only, so it should not be here
h3{
font-size: 1.5rem; font-size: 1.5rem;
color: #333; color: #333;
margin-bottom: 1.2rem; margin-bottom: 1.2rem;
border-bottom: 2px solid #ddd; border-bottom: 2px solid #ddd;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
}
#agenda {
padding: 20px;
background-color: white;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
} }
#agenda {
padding: 20px;
background-color: white;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}
/* Table Styling */ /* Table Styling */
table { table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
text-align: left; text-align: left;
margin-top: 20px; margin-top: 20px;
border: 1px solid #ccc; border: 1px solid #ccc;
}
th {
background-color: #f0f2f5;
padding: 12px;
font-weight: 600;
color: #333;
} }
th {
/* Table Body Rows */ background-color: #f0f2f5;
tbody tr { padding: 12px;
font-weight: 600;
color: #333;
}
/* Table Body Rows */
tbody tr {
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
transition: background-color 0.2s ease; /* Smooth hover effect */ transition: background-color 0.2s ease; /* Smooth hover effect */
} }
tbody tr:hover { tbody tr:hover {
background-color: #f9f9f9; /* Highlight row on hover */ background-color: #f9f9f9; /* Highlight row on hover */
} }
/* Cells Styling */ /* Cells Styling */
td { td {
padding: 10px; padding: 10px;
border: 1px solid #eee; border: 1px solid #eee;
font-size: 14px; font-size: 14px;
vertical-align: middle; /* Align text to middle */ vertical-align: middle; /* Align text to middle */
} }
/* First Column Styling */ /* First Column Styling */
td:first-child { td:first-child {
text-align: center; text-align: center;
width: 50px; /* Adjust width as needed */ width: 50px; /* Adjust width as needed */
} }
</style>
</style>

View File

@ -1,35 +1,35 @@
<script lang="ts" setup> <script lang="ts" setup>
Review

The order changed here

The order changed here
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import {jwtDecode} from "jwt-decode"; // i hope this doesn't break the code later import { jwtDecode } from "jwt-decode"; // i hope this doesn't break the code later
import { store } from "../main.ts"; import { store } from "../main.ts";
import { callApi } from "@/services/api.ts"; import { callApi } from "@/services/api.ts";
const router = useRouter(); const router = useRouter();
type TokenPayload = { type TokenPayload = {
realm_access?: { realm_access?: {
roles?: string[]; roles?: string[];
}; };
}; };
const customRequest = ref(''); const customRequest = ref("");
onMounted(() => { onMounted(() => {
if (store.authenticated && store.user.token) { if (store.authenticated && store.user.token) {
try { try {
const decoded = jwtDecode<TokenPayload>(store.user.token); const decoded = jwtDecode<TokenPayload>(store.user.token);
const roles = decoded.realm_access?.roles || []; const roles = decoded.realm_access?.roles || [];
if (roles.includes("MyINPulse-admin")) { if (roles.includes("MyINPulse-admin")) {
router.push("/"); router.push("/");
} else if (roles.includes("MyINPulse-entrepreneur")) { } else if (roles.includes("MyINPulse-entrepreneur")) {
router.push("/leanCanva"); router.push("/leanCanva");
} }
} catch (err) { } catch (err) {
console.error("Failed to decode token", err); console.error("Failed to decode token", err);
}
} }
}
}); });
/* /*
Review

should this commented part really stay

should this commented part really stay
const loading = ref(false); const loading = ref(false);
@ -40,145 +40,155 @@ const callApiWithLoading = async (path: string) => {
loading.value = false; loading.value = false;
}; };
*/ */
</script> </script>
<template> <template>
<error-wrapper></error-wrapper> <error-wrapper></error-wrapper>
<div class="auth-container"> <div class="auth-container">
<div class="auth-card"> <div class="auth-card">
<h1>Bienvenue</h1> <h1>Bienvenue</h1>
<div class="status" :class="store.authenticated ? 'success' : 'error'"> <div
<p> class="status"
{{ store.authenticated ? '✅ Authenticated' : '❌ Not Authenticated' }} :class="store.authenticated ? 'success' : 'error'"
</p> >
<p>
{{
store.authenticated
? "✅ Authenticated"
: "❌ Not Authenticated"
}}
</p>
</div>
<div class="actions">
<button @click="store.login">Login</button>
<button @click="store.logout">Logout</button>
<button @click="store.signup">Signup-admin</button>
<button @click="store.signup">Signup-Entrepreneur</button>
<button @click="store.refreshUserToken">Refresh Token</button>
</div>
Review

This should not be shown to the user

This should not be shown to the user
<div v-if="store.authenticated" class="token-section">
<p><strong>Access Token:</strong></p>
<pre>{{ store.user.token }}</pre>
<p><strong>Refresh Token:</strong></p>
<pre>{{ store.user.refreshToken }}</pre>
</div>
Review

same, it should be hidden

same, it should be hidden
<div class="api-calls">
<h2>Test API Calls</h2>
<button @click="callApi('random')">
Call Entrepreneur API
</button>
<button @click="callApi('random2')">Call Admin API</button>
<button @click="callApi('unauth/dev')">Call Unauth API</button>
<div class="custom-call">
<input
v-model="customRequest"
placeholder="Custom endpoint"
/>
<button @click="callApi(customRequest)">Call</button>
</div>
</div>
</div> </div>
<div class="actions">
<button @click="store.login">Login</button>
<button @click="store.logout">Logout</button>
<button @click="store.signup">Signup-admin</button>
<button @click="store.signup">Signup-Entrepreneur</button>
<button @click="store.refreshUserToken">Refresh Token</button>
</div>
<div v-if="store.authenticated" class="token-section" >
<p><strong>Access Token:</strong></p>
<pre>{{ store.user.token }}</pre>
<p><strong>Refresh Token:</strong></p>
<pre>{{ store.user.refreshToken }}</pre>
</div>
<div class="api-calls">
<h2>Test API Calls</h2>
<button @click="callApi('random')">Call Entrepreneur API</button>
<button @click="callApi('random2')">Call Admin API</button>
<button @click="callApi('unauth/dev')">Call Unauth API</button>
<div class="custom-call">
<input v-model="customRequest" placeholder="Custom endpoint" />
<button @click="callApi(customRequest)">Call</button>
</div>
</div>
</div>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.auth-container { .auth-container {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding: 3rem 1rem; padding: 3rem 1rem;
min-height: 100vh; min-height: 100vh;
background-color: #eef1f5; background-color: #eef1f5;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
} }
.auth-card { .auth-card {
background: white; background: white;
padding: 2rem; padding: 2rem;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
width: 100%; width: 100%;
max-width: 600px; max-width: 600px;
} }
Review

global style once again

global style once again
h1 { h1 {
text-align: center; text-align: center;
margin-bottom: 1rem; margin-bottom: 1rem;
color: #333; color: #333;
} }
.status { .status {
text-align: center; text-align: center;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
font-weight: bold; font-weight: bold;
} }
.success { .success {
color: green; color: green;
} }
.error { .error {
color: red; color: red;
} }
.actions { .actions {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 1rem; gap: 1rem;
justify-content: center; justify-content: center;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
.actions button { .actions button {
padding: 0.6rem 1rem; padding: 0.6rem 1rem;
background-color: #4a90e2; background-color: #4a90e2;
border: none; border: none;
color: white; color: white;
border-radius: 8px; border-radius: 8px;
cursor: pointer; cursor: pointer;
transition: background-color 0.2s; transition: background-color 0.2s;
} }
.actions button:hover { .actions button:hover {
background-color: #357abd; background-color: #357abd;
} }
.token-section pre { .token-section pre {
background: #f6f8fa; background: #f6f8fa;
padding: 0.5rem; padding: 0.5rem;
overflow-x: auto; overflow-x: auto;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 6px; border-radius: 6px;
margin-bottom: 1rem; margin-bottom: 1rem;
font-size: 0.85rem; font-size: 0.85rem;
} }
.api-calls { .api-calls {
margin-top: 2rem; margin-top: 2rem;
} }
.api-calls h2 { .api-calls h2 {
margin-bottom: 1rem; margin-bottom: 1rem;
color: #444; color: #444;
font-size: 1.1rem; font-size: 1.1rem;
} }
.api-calls button { .api-calls button {
margin-right: 0.5rem; margin-right: 0.5rem;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.custom-call { .custom-call {
margin-top: 1rem; margin-top: 1rem;
display: flex; display: flex;
gap: 0.5rem; gap: 0.5rem;
} }
.custom-call input { .custom-call input {
flex: 1; flex: 1;
padding: 0.5rem; padding: 0.5rem;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 6px; border-radius: 6px;
} }
/* /*
.status { .status {
@ -190,9 +200,7 @@ h1 {
} }
*/ */
.status.error { .status.error {
background-color: #ffe2e2; background-color: #ffe2e2;
color: #c62828; color: #c62828;
} }
</style> </style>

View File

@ -5,129 +5,125 @@
<h2>{{ projectName }}</h2> <h2>{{ projectName }}</h2>
<p>Projet mis le: {{ creationDate }}</p> <p>Projet mis le: {{ creationDate }}</p>
</div> </div>
<div class="project-buttons"> <div class="project-buttons">
<button id="accept" @click="acceptProject">Accepter</button> <button id="accept" @click="acceptProject">Accepter</button>
<button id="refus" @click="refuseProject">Refuser</button> <button id="refus" @click="refuseProject">Refuser</button>
</div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineProps } from "vue"; import { defineProps } from "vue";
import { postApi } from "@/services/api"; import { postApi } from "@/services/api";
import { addNewMessage, color } from "@/services/popupDisplayer"; import { addNewMessage, color } from "@/services/popupDisplayer";
const props = defineProps<{ const props = defineProps<{
projectName: string; projectName: string;
creationDate: string; creationDate: string;
}>(); }>();
const URI = "/admin/projects/pending/decision"; const URI = "/admin/projects/pending/decision";
const sendDecision = (decision: "true" | "false") => { const sendDecision = (decision: "true" | "false") => {
postApi( postApi(
URI, URI,
{ {
projectName: props.projectName, projectName: props.projectName,
decision, decision,
}, },
() => { () => {
addNewMessage( addNewMessage(
`Projet ${props.projectName} ${decision === "true" ? "accepté" : "refusé"}`, `Projet ${props.projectName} ${decision === "true" ? "accepté" : "refusé"}`,
color.Green color.Green
); );
}, },
(err) => { (err) => {
addNewMessage(`Erreur lors de la décision`, color.Red); addNewMessage(`Erreur lors de la décision`, color.Red);
console.error(err); console.error(err);
} }
); );
}; };
const acceptProject = () => sendDecision("true"); const acceptProject = () => sendDecision("true");
const refuseProject = () => sendDecision("false"); const refuseProject = () => sendDecision("false");
</script> </script>
<style scoped> <style scoped>
.project { .project {
background: linear-gradient(to right, #f8f9fb, #ffffff); background: linear-gradient(to right, #f8f9fb, #ffffff);
border: 1px solid #e0e0e0; border: 1px solid #e0e0e0;
border-radius: 16px; border-radius: 16px;
padding: 1.5rem; padding: 1.5rem;
margin: 1.5rem 0; margin: 1.5rem 0;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
transition: box-shadow 0.3s ease; transition: box-shadow 0.3s ease;
} }
.project:hover { .project:hover {
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08); box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
} }
.project-header { .project-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
gap: 1rem; gap: 1rem;
} }
.project-title { .project-title {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.project-title h2 { .project-title h2 {
font-size: 1.25rem; font-size: 1.25rem;
color: #222; color: #222;
margin: 0; margin: 0;
font-weight: 600; font-weight: 600;
} }
.project-title p { .project-title p {
font-size: 0.9rem; font-size: 0.9rem;
color: #666; color: #666;
margin-top: 0.25rem; margin-top: 0.25rem;
} }
.project-buttons { .project-buttons {
display: flex; display: flex;
gap: 0.75rem; gap: 0.75rem;
} }
button { button {
padding: 0.5rem 1.1rem; padding: 0.5rem 1.1rem;
color: white; color: white;
border: none; border: none;
border-radius: 8px; border-radius: 8px;
font-size: 0.9rem; font-size: 0.9rem;
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
transition: background-color 0.2s ease, transform 0.2s ease; transition:
background-color 0.2s ease,
transform 0.2s ease;
} }
#accept { #accept {
background-color: #4CAF50; background-color: #4caf50;
} }
#accept:hover { #accept:hover {
background-color: #3e8e41; background-color: #3e8e41;
transform: translateY(-2px); transform: translateY(-2px);
} }
#refus { #refus {
background-color: #e74c3c; background-color: #e74c3c;
} }
#refus:hover { #refus:hover {
background-color: #c0392b; background-color: #c0392b;
transform: translateY(-2px); transform: translateY(-2px);
} }
</style>
</style>

View File

@ -1,37 +1,41 @@
<template> <template>
<div class="project" @click="goToLink" > <div class="project" @click="goToLink">
<div class="project-header"> <div class="project-header">
<h2 >{{ projectName }}</h2> <h2>{{ projectName }}</h2>
<div class="header-actions"> <div class="header-actions">
<div class="dropdown-wrapper"> <div class="dropdown-wrapper">
<button class="contact-button" @click="toggleDropdown">Contact</button> <button class="contact-button" @click="toggleDropdown">
<div class="contact-dropdown" :class="{ 'dropdown-visible': isDropdownOpen }"> Contact
<button @click="contactAll">Contacter tous</button>
<button
v-for="(email, index) in entrepreneurEmails"
:key="index"
@click="contactSingle(email)"
>
{{ email }}
</button> </button>
</div> <div
class="contact-dropdown"
:class="{ 'dropdown-visible': isDropdownOpen }"
>
<button @click="contactAll">Contacter tous</button>
<button
v-for="(email, index) in entrepreneurEmails"
:key="index"
@click="contactSingle(email)"
>
{{ email }}
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="project-body"> <div class="project-body">
<ul> <ul>
<li v-for="(name, index) in listName" :key="index">{{ name }}</li> <li v-for="(name, index) in listName" :key="index">
{{ name }}
</li>
</ul> </ul>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineProps } from "vue"; import { defineProps } from "vue";
import { useRouter } from 'vue-router' import { useRouter } from "vue-router";
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;
@ -46,188 +50,200 @@ const props = defineProps<{
const router = useRouter(); const router = useRouter();
const goToLink = () => { const goToLink = () => {
if (props.projectLink) { if (props.projectLink) {
router.push(props.projectLink); router.push(props.projectLink);
} }
}; };
type Entrepreneur = { type Entrepreneur = {
idUser: number; idUser: number;
userSurname: string; userSurname: string;
userName: string; userName: string;
primaryMail: string; primaryMail: string;
secondaryMail: string; secondaryMail: string;
phoneNumber: string; phoneNumber: string;
school: string; school: string;
course: string; course: string;
sneeStatus: boolean; sneeStatus: boolean;
}; };
const isDropdownOpen = ref(false); const isDropdownOpen = ref(false);
const entrepreneurEmails = ref<string[]>([]); 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 (
try { projectId: number,
const responseData: Entrepreneur[] = useMock useMock = IS_MOCK_MODE
? await mockFetchEntrepreneurs(projectId) ) => {
: (await axios.get(`http://localhost:5000/shared/projects/entrepreneurs/${projectId}`)).data; try {
const responseData: Entrepreneur[] = useMock
? await mockFetchEntrepreneurs(projectId)
: (
await axios.get(
`http://localhost:5000/shared/projects/entrepreneurs/${projectId}`
)
).data;
if (responseData.length > 0) { if (responseData.length > 0) {
adnane marked this conversation as resolved Outdated
Outdated
Review

We should not use axios.get EVER, it does not send the authentication token.

We should not use axios.get EVER, it does not send the authentication token.
entrepreneurEmails.value = responseData.map((item: Entrepreneur) => item.primaryMail); entrepreneurEmails.value = responseData.map(
} else { (item: Entrepreneur) => item.primaryMail
console.warn("Aucun entrepreneur trouvé."); );
} else {
console.warn("Aucun entrepreneur trouvé.");
}
} catch (error) {
console.error(
"Erreur lors de la récupération des entrepreneurs :",
error
);
} }
} catch (error) {
console.error("Erreur lors de la récupération des entrepreneurs :", error);
}
}; };
// Fonction de simulation de l'API // Fonction de simulation de l'API
const mockFetchEntrepreneurs = async (projectId :number) => { const mockFetchEntrepreneurs = async (projectId: number) => {
console.log(`Mock fetch pour projectId: ${projectId}`); console.log(`Mock fetch pour projectId: ${projectId}`);
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(() => { setTimeout(() => {
resolve([ resolve([
{ {
idUser: 1, idUser: 1,
userSurname: "Doe", userSurname: "Doe",
userName: "John", userName: "John",
primaryMail: "john.doe@example.com", primaryMail: "john.doe@example.com",
secondaryMail: "johndoe@backup.com", secondaryMail: "johndoe@backup.com",
adnane marked this conversation as resolved Outdated
Outdated
Review

I don't love the fact that the mock tests are in the code here, it should be better to have a server. It's not a big probleme though

I don't love the fact that the mock tests are in the code here, it should be better to have a server. It's not a big probleme though
phoneNumber: "612345678", phoneNumber: "612345678",
school: "ENSEIRB", school: "ENSEIRB",
course: "Info", course: "Info",
sneeStatus: false sneeStatus: false,
}, },
{ {
idUser: 2, idUser: 2,
userSurname: "Smith", userSurname: "Smith",
userName: "Jane", userName: "Jane",
primaryMail: "jane.smith@example.com", primaryMail: "jane.smith@example.com",
secondaryMail: "janesmith@backup.com", secondaryMail: "janesmith@backup.com",
phoneNumber: "698765432", phoneNumber: "698765432",
school: "ENSEIRB", school: "ENSEIRB",
course: "Info", course: "Info",
sneeStatus: true sneeStatus: true,
} },
]); ]);
}, 500); }, 500);
}); });
}; };
const contactAll = () => { const contactAll = () => {
const allEmails = entrepreneurEmails.value.join(", "); const allEmails = entrepreneurEmails.value.join(", ");
navigator.clipboard.writeText(allEmails) navigator.clipboard
.then(() => { .writeText(allEmails)
alert("Tous les emails copiés dans le presse-papiers !"); .then(() => {
window.open("https://partage.bordeaux-inp.fr/", "_blank"); alert("Tous les emails copiés dans le presse-papiers !");
}) window.open("https://partage.bordeaux-inp.fr/", "_blank");
.catch(err => { })
console.error("Erreur lors de la copie :", err); .catch((err) => {
}); console.error("Erreur lors de la copie :", err);
});
}; };
const contactSingle = (email: string) => { const contactSingle = (email: string) => {
adnane marked this conversation as resolved Outdated
Outdated
Review

I personally hate that, can't we use an error mesage in green ?

I personally hate that, can't we use an error mesage in green ?

ok

ok
navigator.clipboard.writeText(email) navigator.clipboard
.then(() => { .writeText(email)
alert(`Adresse copiée : ${email}`); .then(() => {
window.open("https://partage.bordeaux-inp.fr/", "_blank"); alert(`Adresse copiée : ${email}`);
}) window.open("https://partage.bordeaux-inp.fr/", "_blank");
.catch(err => { })
console.error("Erreur lors de la copie :", err); .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>
<style scoped> <style scoped>
.project { .project {
background: linear-gradient(to right, #f8f9fb, #ffffff); background: linear-gradient(to right, #f8f9fb, #ffffff);
border: 1px solid #e0e0e0; border: 1px solid #e0e0e0;
border-radius: 16px; border-radius: 16px;
padding: 1.5rem; padding: 1.5rem;
margin: 1.5rem 0; margin: 1.5rem 0;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
transition: box-shadow 0.3s ease; transition: box-shadow 0.3s ease;
cursor: pointer; cursor: pointer;
} }
.project:hover { .project:hover {
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08); box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
} }
.project-header { .project-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.5rem; gap: 0.5rem;
} }
.project-header h2 { .project-header h2 {
font-size: 1.25rem; font-size: 1.25rem;
color: #222; color: #222;
margin: 0; margin: 0;
font-weight: 600; font-weight: 600;
} }
.project-buttons { .project-buttons {
display: flex; display: flex;
gap: 0.5rem; gap: 0.5rem;
} }
.contact-btn { .contact-btn {
background-color: #007bff; background-color: #007bff;
color: #fff; color: #fff;
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
border: none; border: none;
border-radius: 8px; border-radius: 8px;
font-size: 0.9rem; font-size: 0.9rem;
font-weight: 500; font-weight: 500;
transition: background-color 0.2s ease, transform 0.2s ease; transition:
background-color 0.2s ease,
transform 0.2s ease;
} }
.contact-btn:hover { .contact-btn:hover {
background-color: #0056b3; background-color: #0056b3;
transform: translateY(-2px); transform: translateY(-2px);
} }
.project-body { .project-body {
margin-top: 1rem; margin-top: 1rem;
} }
.project-body ul { .project-body ul {
list-style-type: disc; list-style-type: disc;
padding-left: 1.25rem; padding-left: 1.25rem;
margin: 0; margin: 0;
} }
.project-body ul li { .project-body ul li {
font-size: 0.95rem; font-size: 0.95rem;
color: #555; color: #555;
line-height: 1.6; line-height: 1.6;
} }
adnane marked this conversation as resolved Outdated
Outdated
Review

generic style once again

generic style once again
button {
button { padding: 10px 15px;
padding: 10px 15px; background-color: #007bff;
background-color: #007bff; color: white;
color: white; border: none;
border: none; cursor: pointer;
cursor: pointer; border-radius: 5px;
border-radius: 5px;
} }
button:hover { button:hover {
background-color: #0056b3; background-color: #0056b3;
} }
</style>
</style>

View File

@ -1,47 +1,69 @@
<template> <template>
<div :class="['cell', { expanded }]" @click="handleClick"> <div :class="['cell', { expanded }]" @click="handleClick">
<h3 class="fs-5 fw-medium">{{ titleText }}</h3> <h3 class="fs-5 fw-medium">{{ titleText }}</h3>
<div v-for="(desc, index) in currentDescriptions" :key="index" class="section-bloc"> <div
v-for="(desc, index) in currentDescriptions"
:key="index"
class="section-bloc"
>
<!-- ADMIN -------------------------------------------------------------------------------------------->
<!-- ADMIN --------------------------------------------------------------------------------------------> <template v-if="IS_ADMIN">
<div class="description">
<p class="m-0">{{ desc }}</p>
</div>
</template>
<template v-if="IS_ADMIN"> <!-- ENTREP ------------------------------------------------------------------------------------------->
<div class="description">
<p class="m-0">{{ desc }}</p> <template v-if="!IS_ADMIN">
<!-- Mode affichage -->
<template v-if="!isEditing[index]">
<div class="description">
<p class="m-0">{{ desc }}</p>
</div>
<div class="button-container">
<button
v-if="expanded"
class="edit-button"
@click.stop="startEditing(index)"
>
Éditer
</button>
</div>
</template>
<!-- Mode édition -->
<template v-else>
<textarea
v-model="editedDescriptions[index]"
class="edit-input"
></textarea>
<div class="button-container">
<button
class="save-button"
@click.stop="saveEdit(index)"
>
Enregistrer
</button>
<button
class="cancel-button"
@click.stop="cancelEdit(index)"
>
Annuler
</button>
</div>
</template>
</template>
</div> </div>
</template> <!---------------------------------------------------------------------------------------------------->
<template v-if="expanded">
<!-- ENTREP -------------------------------------------------------------------------------------------> <div class="canvas-exit-hint">
Cliquez n'importe pour quitter le canvas
<template v-if="!IS_ADMIN"> </div>
<!-- Mode affichage -->
<template v-if="!isEditing[index]">
<div class="description">
<p class="m-0">{{ desc }}</p>
</div>
<div class="button-container">
<button v-if="expanded" class="edit-button" @click.stop="startEditing(index)">Éditer</button>
</div>
</template> </template>
<!-- Mode édition -->
<template v-else>
<textarea v-model="editedDescriptions[index]" class="edit-input"></textarea>
<div class="button-container">
<button class="save-button" @click.stop="saveEdit(index)">Enregistrer</button>
<button class="cancel-button" @click.stop="cancelEdit(index)">Annuler</button>
</div>
</template>
</template>
</div> </div>
<!---------------------------------------------------------------------------------------------------->
<template v-if="expanded">
<div class="canvas-exit-hint">
Cliquez n'importe pour quitter le canvas
</div>
</template>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -49,14 +71,14 @@ import { ref, defineProps, onMounted } from "vue";
import axios from "axios"; import axios from "axios";
import { axiosInstance } from "@/services/api.ts"; import { axiosInstance } from "@/services/api.ts";
const IS_MOCK_MODE = true; const IS_MOCK_MODE = true;
const props = defineProps<{ const props = defineProps<{
projectId: number; projectId: number;
title: number; title: number;
titleText: string; titleText: string;
description: string; description: string;
isAdmin: number; isAdmin: number;
}>(); }>();
const IS_ADMIN = props.isAdmin; const IS_ADMIN = props.isAdmin;
@ -68,10 +90,9 @@ const editedDescriptions = ref<string[]>([]);
const isEditing = ref<boolean[]>([]); const isEditing = ref<boolean[]>([]);
onMounted(() => { onMounted(() => {
fetchData(props.projectId, props.title, "NaN", IS_MOCK_MODE); fetchData(props.projectId, props.title, "NaN", IS_MOCK_MODE);
}); });
/* FOR LOCAL DATABASE /* FOR LOCAL DATABASE
const fetchData = async () => { const fetchData = async () => {
try { try {
@ -111,59 +132,67 @@ const fetchData = async (projectId: number, title: number, date: string, useMock
*/ */
// Fonction fetchData avec possibilité d'utiliser le mock // Fonction fetchData avec possibilité d'utiliser le mock
const fetchData = async (projectId: number, title: number, date: string, useMock = false) => { const fetchData = async (
try { projectId: number,
const responseData = useMock title: number,
? await mockFetch(projectId, title, date) date: string,
: (await axiosInstance.get<{ txt: string }[]>( useMock = false
`/shared/projects/lcsection/${projectId}/${title}/${date}` ) => {
)).data; try {
const responseData = useMock
? await mockFetch(projectId, title, date)
: (
await axiosInstance.get<{ txt: string }[]>(
adnane marked this conversation as resolved Outdated
Outdated
Review

be careful with axiosInstance, but i may be fine

be careful with axiosInstance, but i may be fine
`/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);
editedDescriptions.value = [...currentDescriptions.value]; editedDescriptions.value = [...currentDescriptions.value];
isEditing.value = Array(responseData.length).fill(false); isEditing.value = Array(responseData.length).fill(false);
} else { } else {
console.warn("Aucune donnée reçue."); console.warn("Aucune donnée reçue.");
}
} catch (error) {
console.error("Erreur lors de la récupération des données :", error);
} }
} catch (error) {
console.error("Erreur lors de la récupération des données :", error);
}
}; };
// 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}`
);
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."}, { txt: "Ceci est une description 1 pour tester le front." },
{txt: "Deuxième description."}, { txt: "Deuxième description." },
{txt: "Troisième description."} { txt: "Troisième description." },
]); ]);
}, 500); // Simule un délai réseau de 500ms }, 500); // Simule un délai réseau de 500ms
}); });
}; };
// Utilisation du mock dans handleClick pour tester sans serveur // Utilisation du mock dans handleClick pour tester sans serveur
const handleClick = async () => { const handleClick = async () => {
if (!expanded.value) { if (!expanded.value) {
await fetchData(props.projectId, props.title, "NaN", IS_MOCK_MODE); await fetchData(props.projectId, props.title, "NaN", IS_MOCK_MODE);
} else if (!isEditing.value.includes(true)) { } else if (!isEditing.value.includes(true)) {
// Réinitialiser les descriptions si aucune édition n'est en cours // Réinitialiser les descriptions si aucune édition n'est en cours
currentDescriptions.value = [props.description]; currentDescriptions.value = [props.description];
editedDescriptions.value = [props.description]; editedDescriptions.value = [props.description];
} }
if (!isEditing.value.includes(true)) { if (!isEditing.value.includes(true)) {
expanded.value = !expanded.value; expanded.value = !expanded.value;
} }
}; };
const startEditing = (index: number) => { const startEditing = (index: number) => {
isEditing.value[index] = true; isEditing.value[index] = true;
}; };
/* /*
@ -184,213 +213,208 @@ const saveEdit = async (index: number) => {
*/ */
const saveEdit = async (index: number) => { const saveEdit = async (index: number) => {
if (IS_MOCK_MODE) { if (IS_MOCK_MODE) {
await mockSaveEdit(index); await mockSaveEdit(index);
} else { } else {
try { try {
const id = index + 1; const id = index + 1;
await axios.put(`http://localhost:5000/data/${id}`, { await axios.put(`http://localhost:5000/data/${id}`, {
adnane marked this conversation as resolved Outdated
Outdated
Review

same

same
canva_data: editedDescriptions.value[index] canva_data: editedDescriptions.value[index],
}); });
// Mettre à jour l'affichage local après la mise à jour réussie // Mettre à jour l'affichage local après la mise à jour réussie
currentDescriptions.value[index] = editedDescriptions.value[index]; currentDescriptions.value[index] = editedDescriptions.value[index];
isEditing.value[index] = false; isEditing.value[index] = false;
} catch (error) { } catch (error) {
console.error("Erreur lors de la mise à jour des données :", error); console.error("Erreur lors de la mise à jour des données :", error);
}
} }
}
}; };
// Fonction de mock pour l'enregistrement // Fonction de mock pour l'enregistrement
const mockSaveEdit = async (index: number) => { const mockSaveEdit = async (index: number) => {
try { try {
const id = index + 1; const id = index + 1;
console.log(`Mock save pour l'ID ${id} avec la description : ${editedDescriptions.value[index]}`); console.log(
`Mock save pour l'ID ${id} avec la description : ${editedDescriptions.value[index]}`
);
await new Promise((resolve) => setTimeout(resolve, 500)); // Simulation de délai réseau await new Promise((resolve) => setTimeout(resolve, 500)); // Simulation de délai réseau
// Mettre à jour l'affichage local après la mise à jour réussie // Mettre à jour l'affichage local après la mise à jour réussie
currentDescriptions.value[index] = editedDescriptions.value[index]; currentDescriptions.value[index] = editedDescriptions.value[index];
isEditing.value[index] = false; isEditing.value[index] = false;
} catch (error) { } catch (error) {
console.error("Erreur lors de la mise à jour des données mockées :", error); console.error(
} "Erreur lors de la mise à jour des données mockées :",
error
);
}
}; };
const cancelEdit = (index: number) => { const cancelEdit = (index: number) => {
editedDescriptions.value[index] = currentDescriptions.value[index]; editedDescriptions.value[index] = currentDescriptions.value[index];
isEditing.value[index] = false; isEditing.value[index] = false;
}; };
</script> </script>
<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;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
text-align: center; text-align: center;
transition: all 0.3s ease; transition: all 0.3s ease;
cursor: pointer; cursor: pointer;
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;
} }
.cell:not(.expanded):hover { .cell:not(.expanded):hover {
transform: scale(1.05); transform: scale(1.05);
box-shadow: 0 8px 9px rgba(0, 0, 0, 0.2); box-shadow: 0 8px 9px rgba(0, 0, 0, 0.2);
} }
.cell h3 { .cell h3 {
font-size: 15px; font-size: 15px;
font-weight: 500; font-weight: 500;
font-family: 'Arial', sans-serif; font-family: "Arial", sans-serif;
} }
.p { .p {
font-size: 10px; font-size: 10px;
color: #666; color: #666;
font-family: 'Arial', sans-serif; font-family: "Arial", sans-serif;
} }
.expanded { .expanded {
padding-top: 10%; padding-top: 10%;
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: white; background: white;
z-index: 10; z-index: 10;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
} }
.description { .description {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
text-align: center; text-align: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
font-size: 16px; font-size: 16px;
margin-top: 10px; margin-top: 10px;
margin-left: 2%; margin-left: 2%;
margin-right: 4%; margin-right: 4%;
} }
.description + .p { .description + .p {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
text-align: center; text-align: center;
} }
.edit-input { .edit-input {
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 10px; padding: 10px;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 5px; border-radius: 5px;
margin-top: 10px; margin-top: 10px;
box-sizing: border-box; box-sizing: border-box;
margin-left: 2%; margin-left: 2%;
} }
.button-container { .button-container {
display: block; display: block;
margin-top: 20px; margin-top: 20px;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
padding-right: 1%; padding-right: 1%;
} }
.section-bloc,
.section-bloc ,.editing-section-bloc { .editing-section-bloc {
width: 100%; width: 100%;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
display: flex; display: flex;
margin-right: 10%; margin-right: 10%;
margin: 10px; margin: 10px;
}
.edit-button {
width: 100px;
height: 40px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s ease;
font-size: 12px;
margin-right: 20px;
}
.save-button, .cancel-button {
width: 100px;
height: 40px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s ease;
font-size: 12px;
margin-bottom: 5px;
} }
.edit-button { .edit-button {
background-color: #007bff; width: 100px;
color: white; height: 40px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s ease;
font-size: 12px;
margin-right: 20px;
}
.save-button,
.cancel-button {
width: 100px;
height: 40px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s ease;
font-size: 12px;
margin-bottom: 5px;
}
.edit-button {
background-color: #007bff;
color: white;
} }
.save-button { .save-button {
background-color: #28a745; background-color: #28a745;
color: white; color: white;
} }
.cancel-button { .cancel-button {
background-color: #dc3545; background-color: #dc3545;
color: white; color: white;
} }
.edit-button:hover { .edit-button:hover {
background-color: #0056b3; background-color: #0056b3;
} }
.save-button:hover { .save-button:hover {
background-color: #218838; background-color: #218838;
} }
.cancel-button:hover { .cancel-button:hover {
background-color: #c82333; background-color: #c82333;
} }
.canvas-exit-hint { .canvas-exit-hint {
font-size: 0.75rem; font-size: 0.75rem;
color: #666; color: #666;
position: fixed; position: fixed;
bottom: 10px; bottom: 10px;
left: 0; left: 0;
width: 100%; width: 100%;
text-align: center; text-align: center;
z-index: 1000; z-index: 1000;
} }
</style>
</style>

View File

@ -1,27 +1,31 @@
<template> <template>
adnane marked this conversation as resolved
Review

Doesn't this header overlap with the main app canvas ?

Doesn't this header overlap with the main app canvas ?
<header class="header"> <header class="header">
<img src="../icons/logo inpulse.png" alt="INPulse Logo" class="logo" /> <img src="../icons/logo inpulse.png" alt="INPulse Logo" class="logo" />
<div class="header-actions"> <div class="header-actions">
<div class="dropdown-wrapper"> <div class="dropdown-wrapper">
<button class="contact-button" @click="toggleDropdown">Contact</button> <button class="contact-button" @click="toggleDropdown">
<div class="contact-dropdown" :class="{ 'dropdown-visible': isDropdownOpen }"> Contact
<button @click="contactAll">Contacter tous</button> </button>
<button <div
v-for="(email, index) in entrepreneurEmails" class="contact-dropdown"
:key="index" :class="{ 'dropdown-visible': isDropdownOpen }"
@click="contactSingle(email)" >
> <button @click="contactAll">Contacter tous</button>
{{ email }} <button
</button> v-for="(email, index) in entrepreneurEmails"
:key="index"
@click="contactSingle(email)"
>
{{ email }}
</button>
</div>
</div>
<RouterLink to="/" class="return-button">Retour</RouterLink>
</div> </div>
</div> </header>
<RouterLink to="/" class="return-button">Retour</RouterLink>
</div>
</header>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import axios from "axios"; import axios from "axios";
adnane marked this conversation as resolved Outdated
Outdated
Review

I won't talk again about axios

I won't talk again about axios
@ -29,100 +33,114 @@ 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 = {
idUser: number; idUser: number;
userSurname: string; userSurname: string;
userName: string; userName: string;
primaryMail: string; primaryMail: string;
secondaryMail: string; secondaryMail: string;
phoneNumber: string; phoneNumber: string;
school: string; school: string;
course: string; course: string;
sneeStatus: boolean; sneeStatus: boolean;
}; };
const isDropdownOpen = ref(false); const isDropdownOpen = ref(false);
const entrepreneurEmails = ref<string[]>([]); 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 (
try { projectId: number,
const responseData: Entrepreneur[] = useMock useMock = IS_MOCK_MODE
? await mockFetchEntrepreneurs(projectId) ) => {
: (await axios.get(`http://localhost:5000/shared/projects/entrepreneurs/${projectId}`)).data; try {
const responseData: Entrepreneur[] = useMock
? await mockFetchEntrepreneurs(projectId)
: (
await axios.get(
`http://localhost:5000/shared/projects/entrepreneurs/${projectId}`
)
).data;
if (responseData.length > 0) { if (responseData.length > 0) {
entrepreneurEmails.value = responseData.map((item: Entrepreneur) => item.primaryMail); entrepreneurEmails.value = responseData.map(
} else { (item: Entrepreneur) => item.primaryMail
console.warn("Aucun entrepreneur trouvé."); );
} else {
console.warn("Aucun entrepreneur trouvé.");
}
} catch (error) {
console.error(
"Erreur lors de la récupération des entrepreneurs :",
error
);
} }
} catch (error) {
console.error("Erreur lors de la récupération des entrepreneurs :", error);
}
}; };
// Fonction de simulation de l'API // Fonction de simulation de l'API
const mockFetchEntrepreneurs = async (projectId :number) => { const mockFetchEntrepreneurs = async (projectId: number) => {
console.log(`Mock fetch pour projectId: ${projectId}`); console.log(`Mock fetch pour projectId: ${projectId}`);
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(() => { setTimeout(() => {
resolve([ resolve([
{ {
idUser: 1, idUser: 1,
userSurname: "Doe", userSurname: "Doe",
userName: "John", userName: "John",
primaryMail: "john.doe@example.com", primaryMail: "john.doe@example.com",
secondaryMail: "johndoe@backup.com", secondaryMail: "johndoe@backup.com",
phoneNumber: "612345678", phoneNumber: "612345678",
school: "ENSEIRB", school: "ENSEIRB",
course: "Info", course: "Info",
sneeStatus: false sneeStatus: false,
}, },
{ {
idUser: 2, idUser: 2,
userSurname: "Smith", userSurname: "Smith",
userName: "Jane", userName: "Jane",
primaryMail: "jane.smith@example.com", primaryMail: "jane.smith@example.com",
secondaryMail: "janesmith@backup.com", secondaryMail: "janesmith@backup.com",
phoneNumber: "698765432", phoneNumber: "698765432",
school: "ENSEIRB", school: "ENSEIRB",
course: "Info", course: "Info",
sneeStatus: true sneeStatus: true,
} },
]); ]);
}, 500); }, 500);
}); });
}; };
const contactAll = () => { const contactAll = () => {
const allEmails = entrepreneurEmails.value.join(", "); const allEmails = entrepreneurEmails.value.join(", ");
navigator.clipboard.writeText(allEmails) navigator.clipboard
.then(() => { .writeText(allEmails)
alert("Tous les emails copiés dans le presse-papiers !"); .then(() => {
window.open("https://partage.bordeaux-inp.fr/", "_blank"); alert("Tous les emails copiés dans le presse-papiers !");
}) window.open("https://partage.bordeaux-inp.fr/", "_blank");
.catch(err => { })
console.error("Erreur lors de la copie :", err); .catch((err) => {
}); console.error("Erreur lors de la copie :", err);
});
}; };
const contactSingle = (email: string) => { const contactSingle = (email: string) => {
navigator.clipboard.writeText(email) navigator.clipboard
.then(() => { .writeText(email)
alert(`Adresse copiée : ${email}`); .then(() => {
window.open("https://partage.bordeaux-inp.fr/", "_blank"); alert(`Adresse copiée : ${email}`);
}) window.open("https://partage.bordeaux-inp.fr/", "_blank");
.catch(err => { })
console.error("Erreur lors de la copie :", err); .catch((err) => {
}); console.error("Erreur lors de la copie :", err);
});
}; };
/* /*
@ -142,78 +160,75 @@ onMounted(() => fetchEntrepreneurs(props.projectId, IS_MOCK_MODE));
@import "@/components/canvas/style-project.css"; @import "@/components/canvas/style-project.css";
.header { .header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 15px 30px; padding: 15px 30px;
background-color: #f9f9f9; background-color: #f9f9f9;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
} }
.logo { .logo {
height: 50px; height: 50px;
} }
.header-actions { .header-actions {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 20px; gap: 20px;
position: relative; position: relative;
} }
.contact-button, .contact-button,
.return-button { .return-button {
background-color: #009CDE; background-color: #009cde;
color: white; color: white;
border: none; border: none;
padding: 10px 15px; padding: 10px 15px;
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 14px;
border-radius: 5px; border-radius: 5px;
text-decoration: none; text-decoration: none;
transition: background-color 0.2s ease; transition: background-color 0.2s ease;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
} }
.return-button:hover, .return-button:hover,
.contact-button:hover { .contact-button:hover {
background-color: #007bad; background-color: #007bad;
} }
.contact-dropdown { .contact-dropdown {
position: absolute; position: absolute;
top: 100%; top: 100%;
left: 0; left: 0;
background-color: #000; background-color: #000;
color: white; color: white;
box-shadow: 0px 4px 8px rgba(255, 255, 255, 0.2); box-shadow: 0px 4px 8px rgba(255, 255, 255, 0.2);
border-radius: 8px; border-radius: 8px;
padding: 10px; padding: 10px;
margin-top: 5px; margin-top: 5px;
z-index: 1000; z-index: 1000;
min-width: 200px; min-width: 200px;
display: none; display: none;
} }
.contact-dropdown button { .contact-dropdown button {
display: block; display: block;
width: 100%; width: 100%;
padding: 5px; padding: 5px;
text-align: left; text-align: left;
border: none; border: none;
background: none; background: none;
cursor: pointer; cursor: pointer;
color: white; color: white;
} }
.contact-dropdown button:hover { .contact-dropdown button:hover {
background-color: #009CDE; background-color: #009cde;
} }
.contact-dropdown.dropdown-visible { .contact-dropdown.dropdown-visible {
display: block; display: block;
} }
</style> </style>

View File

@ -1,16 +1,16 @@
<template> <template>
<div class="canvas container-fluid"> <div class="canvas container-fluid">
<CanvasItem <CanvasItem
v-for="(item, index) in items" v-for="(item, index) in items"
:key="index" :key="index"
:title="item.title" :title="item.title"
:title-text="item.title_text" :title-text="item.title_text"
:description="item.description" :description="item.description"
:project-id="item.projectId" :project-id="item.projectId"
:class="['canvas-item', item.class, 'card', 'shadow', 'p-3']" :class="['canvas-item', item.class, 'card', 'shadow', 'p-3']"
:is-admin= props.isAdmin :is-admin="props.isAdmin"
/> />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -18,65 +18,150 @@ import { ref, onMounted } from "vue";
import CanvasItem from "@/components/canvas/CanvasItem.vue"; import CanvasItem from "@/components/canvas/CanvasItem.vue";
const props = defineProps<{ const props = defineProps<{
isAdmin: number; isAdmin: number;
}>(); }>();
const items = ref([ const items = ref([
{ projectId: 1, title: 1, title_text: "1. Problème", description: "3 problèmes essentiels à résoudre pour le client", class: "Probleme" }, {
{ projectId: 1, title: 2, title_text: "2. Segments", description: "Les segments de clientèle visés", class: "Segments" }, projectId: 1,
{ projectId: 1, title: 3, title_text: "3. Valeur", description: "La proposition de valeur", class: "Valeur" }, title: 1,
{ projectId: 1, title: 4, title_text: "4. Solution", description: "Les solutions proposées", class: "Solution" }, title_text: "1. Problème",
{ projectId: 1, title: 5, title_text: "5. Avantage", description: "Les avantages concurrentiels", class: "Avantage" }, description: "3 problèmes essentiels à résoudre pour le client",
{ projectId: 1, title: 6, title_text: "6. Canaux", description: "Les canaux de distribution", class: "Canaux" }, class: "Probleme",
{ projectId: 1, title: 7, title_text: "7. Indicateurs", description: "Les indicateurs clés de performance", class: "Indicateurs" }, },
{ projectId: 1, title: 8, title_text: "8. Coûts", description: "Les coûts associés", class: "Couts" }, {
{ projectId: 1, title: 9, title_text: "9. Revenus", description: "Les sources de revenus", class: "Revenus" } projectId: 1,
title: 2,
title_text: "2. Segments",
description: "Les segments de clientèle visés",
class: "Segments",
},
{
projectId: 1,
title: 3,
title_text: "3. Valeur",
description: "La proposition de valeur",
class: "Valeur",
},
{
projectId: 1,
title: 4,
title_text: "4. Solution",
description: "Les solutions proposées",
class: "Solution",
},
{
projectId: 1,
title: 5,
title_text: "5. Avantage",
description: "Les avantages concurrentiels",
class: "Avantage",
},
{
projectId: 1,
title: 6,
title_text: "6. Canaux",
description: "Les canaux de distribution",
class: "Canaux",
},
{
projectId: 1,
title: 7,
title_text: "7. Indicateurs",
description: "Les indicateurs clés de performance",
class: "Indicateurs",
},
{
projectId: 1,
title: 8,
title_text: "8. Coûts",
description: "Les coûts associés",
class: "Couts",
},
{
projectId: 1,
title: 9,
title_text: "9. Revenus",
description: "Les sources de revenus",
class: "Revenus",
},
]); ]);
onMounted(() => { onMounted(() => {
const bootstrapCss = document.createElement('link') const bootstrapCss = document.createElement("link");
bootstrapCss.rel = 'stylesheet' bootstrapCss.rel = "stylesheet";
bootstrapCss.href = 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css' bootstrapCss.href =
bootstrapCss.integrity = 'sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+Fpc+NC' "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css";
adnane marked this conversation as resolved Outdated
Outdated
Review

Why is bootstrap imported here ?

Why is bootstrap imported here ?
bootstrapCss.crossOrigin = 'anonymous' bootstrapCss.integrity =
document.head.appendChild(bootstrapCss) "sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+Fpc+NC";
bootstrapCss.crossOrigin = "anonymous";
document.head.appendChild(bootstrapCss);
const bootstrapJs = document.createElement('script') const bootstrapJs = document.createElement("script");
bootstrapJs.src = 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js' bootstrapJs.src =
bootstrapJs.integrity = 'sha384-mQ93S0EhrF4Z1nM+fTflmYf0DyzsY5j7F5H3WlClDD6H3WUJh6kxBkF3GDW8n1j6' "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js";
bootstrapJs.crossOrigin = 'anonymous' bootstrapJs.integrity =
document.body.appendChild(bootstrapJs) "sha384-mQ93S0EhrF4Z1nM+fTflmYf0DyzsY5j7F5H3WlClDD6H3WUJh6kxBkF3GDW8n1j6";
}) bootstrapJs.crossOrigin = "anonymous";
document.body.appendChild(bootstrapJs);
});
</script> </script>
<style scoped> <style scoped>
@import "@/components/canvas/style-project.css"; @import "@/components/canvas/style-project.css";
.canvas { .canvas {
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: 12px; gap: 12px;
padding: 30px; padding: 30px;
/*background-color: #f8f9fa;*/ /*background-color: #f8f9fa;*/
position: relative; position: relative;
height: 90vh; height: 90vh;
overflow: auto; overflow: auto;
} }
.Probleme { grid-column: 1 / 3; grid-row: 1 / 5; } .Probleme {
.Segments { grid-column: 9 / 11; grid-row: 1 / 5; } grid-column: 1 / 3;
.Valeur { grid-column: 5 / 7; grid-row: 1 / 5; } grid-row: 1 / 5;
.Solution { grid-column: 3 / 5; grid-row: 1 / 3; } }
.Avantage { grid-column: 7 / 9; grid-row: 1 / 3; } .Segments {
.Canaux { grid-column: 7 / 9; grid-row: 3 / 5; } grid-column: 9 / 11;
.Indicateurs { grid-column: 3 / 5; grid-row: 3 / 5; } grid-row: 1 / 5;
.Couts { grid-column: 1 / 6; grid-row: 5 / 7; } }
.Revenus { grid-column: 6 / 11; grid-row: 5 / 7; } .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;
}
.canvas-item { .canvas-item {
/*background-color: white;*/ /*background-color: white;*/
border: 1px solid #dee2e6; border: 1px solid #dee2e6;
border-radius: 0.5rem; border-radius: 0.5rem;
} }
</style> </style>

View File

@ -2,63 +2,62 @@ body {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
margin: 0; margin: 0;
padding: 10px; padding: 10px;
} }
.row { .row {
display: flex; display: flex;
} }
.cell { .cell {
flex: 1; flex: 1;
border: 1px solid #ddd; border: 1px solid #ddd;
adnane marked this conversation as resolved
Review

it would be better to use variables for colors, but not a big issue either.

it would be better to use variables for colors, but not a big issue either.
padding: 10px; padding: 10px;
text-align: center; text-align: center;
background-color: #f1f1f1; background-color: #f1f1f1;
} }
.produit { .produit {
background-color: #f9e4e4; background-color: #f9e4e4;
} }
.marche { .marche {
background-color: #e4f1f9; background-color: #e4f1f9;
} }
.valeur { .valeur {
background-color: #f9f4e4; background-color: #f9f4e4;
} }
h3 { h3 {
margin: 0; margin: 0;
font-size: 18px; font-size: 18px;
color: #333; color: #333;
} }
p { p {
margin: 5px 0 0; margin: 5px 0 0;
font-size: 14px; font-size: 14px;
} }
body { body {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
margin: 0; margin: 0;
padding: 0; padding: 0;
background-color: #f9f9f9; background-color: #f9f9f9;
} }
h1 img { h1 img {
height: 80px; height: 80px;
margin: 40px; margin: 40px;
text-align: center; text-align: center;
} }
.row { .row {
display: flex; display: flex;
margin-bottom: 10px; margin-bottom: 10px;
} }
#ade { #ade {
max-width: 1200px; max-width: 1200px;
margin: 20px auto; margin: 20px auto;
padding: 20px; padding: 20px;
@ -66,49 +65,51 @@ body {
background-color: #e8f5e9; background-color: #e8f5e9;
border: 2px solid #4caf50; border: 2px solid #4caf50;
border-radius: 10px; border-radius: 10px;
} }
#ade h3 { #ade h3 {
color: #2e7d32; color: #2e7d32;
} }
#ade p { #ade p {
margin: 10px 0; margin: 10px 0;
font-size: 16px; font-size: 16px;
color: #333; color: #333;
} }
header { header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 10px 20px; padding: 10px 20px;
background-color: #fff; background-color: #fff;
border-bottom: 2px solid #ddd; border-bottom: 2px solid #ddd;
} }
header img { header img {
height: 60px; height: 60px;
} }
header .contact-menu { header .contact-menu {
position: relative; position: relative;
} }
.contact-button, .return { .contact-button,
.return {
padding: 10px 15px; padding: 10px 15px;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
background-color: #2196f3; background-color: #2196f3;
color: #fff; color: #fff;
cursor: pointer; cursor: pointer;
} }
.contact-button:hover, .return:hover { .contact-button:hover,
.return:hover {
background-color: #1976d2; background-color: #1976d2;
} }
/* Dropdown styling */ /* Dropdown styling */
.contact-dropdown { .contact-dropdown {
position: absolute; position: absolute;
right: 0; right: 0;
top: 50px; top: 50px;
@ -121,36 +122,35 @@ body {
border-radius: 8px; border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
z-index: 10; z-index: 10;
} }
.contact-dropdown button { .contact-dropdown button {
padding: 8px 12px; padding: 8px 12px;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
background-color: #4caf50; background-color: #4caf50;
color: #fff; color: #fff;
cursor: pointer; cursor: pointer;
} }
.contact-dropdown button:hover { .contact-dropdown button:hover {
background-color: #388e3c; background-color: #388e3c;
} }
.return { .return {
background-color: #f44336; background-color: #f44336;
} }
.return:hover { .return:hover {
background-color: #d32f2f; background-color: #d32f2f;
} }
.header-buttons { .header-buttons {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 15px; gap: 15px;
} }
a {
a{
color: white; color: white;
} }

View File

@ -26,8 +26,7 @@ keycloakService.CallInit(() => {
console.error(e); console.error(e);
createApp(App).mount("#app"); createApp(App).mount("#app");
} }
});
})
// this shit made by me so i can run the canva vue app // this shit made by me so i can run the canva vue app
adnane marked this conversation as resolved Outdated
Outdated
Review

We can't let this kind of comments

We can't let this kind of comments
//createApp(App).use(router).mount('#app'); //createApp(App).use(router).mount('#app');
@ -71,7 +70,4 @@ app.use(VueKeyCloak,{
} ); } );
*/ */
export { store }; export { store };

View File

@ -1,46 +1,46 @@
import { createRouter, createWebHistory } from "vue-router"; import { createRouter, createWebHistory } from "vue-router";
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
routes: [ routes: [
{ {
path: '/test', path: "/test",
name: 'test', name: "test",
// route level code-splitting // route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route // this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited. // which is lazy-loaded when the route is visited.
component: () => import('../views/testComponent.vue'), component: () => import("../views/testComponent.vue"),
}, },
{ {
path: '/login', path: "/login",
name: 'login', name: "login",
component: () => import('../components/LoginComponent.vue'), component: () => import("../components/LoginComponent.vue"),
}, },
{ {
path: '/', path: "/",
name: 'Admin-main', name: "Admin-main",
component: () => import('../views/AdminMain.vue'), component: () => import("../views/AdminMain.vue"),
}, },
// route pour les canvas (made by adnane), in fact the two vue apps are separated for now // route pour les canvas (made by adnane), in fact the two vue apps are separated for now
{ {
path: '/canvas', path: "/canvas",
name: 'canvas', name: "canvas",
component: () => import('../views/CanvasView.vue'), component: () => import("../views/CanvasView.vue"),
}, },
{ {
path: '/signup', path: "/signup",
name: 'signup', name: "signup",
component: () => import('../views/EntrepSignUp.vue'), component: () => import("../views/EntrepSignUp.vue"),
}, },
{ {
path: '/JorCproject', path: "/JorCproject",
adnane marked this conversation as resolved
Review

Can we find a better name ?

Can we find a better name ?
Review

ok

ok
name: 'JorCproject', name: "JorCproject",
component: () => import('../views/JoinOrCreatProjectForEntrep.vue'), component: () => import("../views/JoinOrCreatProjectForEntrep.vue"),
}, },
], ],
}) });
export default router; export default router;

View File

@ -88,5 +88,4 @@ function deleteApi(
.catch(onErrorHandler ?? defaultApiErrorHandler); .catch(onErrorHandler ?? defaultApiErrorHandler);
} }
export { axiosInstance, callApi, postApi, deleteApi }; export { axiosInstance, callApi, postApi, deleteApi };

View File

@ -1,39 +1,38 @@
<template> <template>
<Header /> <Header />
<error-wrapper></error-wrapper> <error-wrapper></error-wrapper>
<div id="container"> <div id="container">
Outdated
Review

it's a bit weird to have content here, if this file replaces main.vue, fine bud don"t put content on it

it's a bit weird to have content here, if this file replaces main.vue, fine bud don"t put content on it
<div id="main"> <div id="main">
<h3> Projet en cours </h3> <h3>Projet en cours</h3>
<ProjectComp <ProjectComp
v-for="(project, index) in projects" v-for="(project, index) in projects"
:key="index" :key="index"
:project-name="project.name" :project-name="project.name"
:list-name="project.members" :list-name="project.members"
:project-link="project.link" :project-link="project.link"
:project-id="0" :project-id="0"
/> />
<div id ="main"> <div id="main">
<h3> Projet en attente </h3> <h3>Projet en attente</h3>
<PendingProjectComponent
v-for="( project, index) in pendingProjects"
:key="index"
:project-name="project.name"
:creation-date="project.creationDate"
/>
</div>
<AddProjectForm/> <PendingProjectComponent
</div> v-for="(project, index) in pendingProjects"
:key="index"
<Agenda :project-r-d-v="rendezVous" /> :project-name="project.name"
:creation-date="project.creationDate"
/>
</div>
<AddProjectForm />
</div>
<Agenda :project-r-d-v="rendezVous" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref/*, onMounted*/ } from "vue"; import { ref /*, onMounted*/ } from "vue";
//import { callApi } from "@/services/api"; //import { callApi } from "@/services/api";
import Header from "../components/HeaderComponent.vue"; import Header from "../components/HeaderComponent.vue";
@ -89,79 +88,73 @@ import AddProjectForm from "@/components/AddProjectForm.vue";
onMounted(fetchProjects); onMounted(fetchProjects);
*/ */
Outdated
Review

what is that for ?

what is that for ?
const projects = ref([ const projects = ref([
{ {
name: "Projet Alpha", name: "Projet Alpha",
link: "/canvas", // to test link: "/canvas", // to test
members: ["Alice", "Bob", "Charlie"], members: ["Alice", "Bob", "Charlie"],
}, },
{ {
name: "Projet Beta", name: "Projet Beta",
link: "./canvas", // to test link: "./canvas", // to test
members: ["David", "Eve", "Frank"], members: ["David", "Eve", "Frank"],
}, },
]); ]);
const pendingProjects = ref([
const pendingProjects = ref ([ { name: "l'eau", creationDate: "26-02-2024" },
{ name: "l'eau", creationDate: "26-02-2024" }, { name: "l'air", creationDate: "09-03-2023" },
{ name: "l'air", creationDate: "09-03-2023" }, ]);
])
const rendezVous = ref([ const rendezVous = ref([
{ projectName: "Projet Alpha", date: "2025-03-10", lieu: "P106" }, { projectName: "Projet Alpha", date: "2025-03-10", lieu: "P106" },
{ projectName: "Projet Beta", date: "2025-04-15", lieu: "Td10" }, { projectName: "Projet Beta", date: "2025-04-15", lieu: "Td10" },
]); ]);
</script> </script>
<style scoped> <style scoped>
#container { #container {
Outdated
Review

same comment for CSS

same comment for CSS
display: grid; display: grid;
grid-template-columns: 3fr 1fr; grid-template-columns: 3fr 1fr;
gap: 2rem; gap: 2rem;
padding: 2rem; padding: 2rem;
background-color: #f4f6f9; background-color: #f4f6f9;
min-height: 100vh; min-height: 100vh;
box-sizing: border-box; box-sizing: border-box;
} }
#main { #main {
background-color: #fff; background-color: #fff;
padding: 2rem; padding: 2rem;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
} }
h3 { h3 {
font-size: 1.5rem; font-size: 1.5rem;
color: #333; color: #333;
margin-bottom: 1.2rem; margin-bottom: 1.2rem;
border-bottom: 2px solid #ddd; border-bottom: 2px solid #ddd;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
} }
button { button {
padding: 10px 15px; padding: 10px 15px;
background-color: #007bff; background-color: #007bff;
color: white; color: white;
border: none; border: none;
cursor: pointer; cursor: pointer;
border-radius: 6px; border-radius: 6px;
font-weight: 500; font-weight: 500;
transition: background-color 0.2s ease; transition: background-color 0.2s ease;
} }
button:hover { button:hover {
background-color: #0056b3; background-color: #0056b3;
} }
/* Add spacing between project sections */ /* Add spacing between project sections */
#main > * + * { #main > * + * {
margin-top: 2rem; margin-top: 2rem;
} }
</style>
</style>

View File

@ -1,32 +1,40 @@
<template> <template>
<div> <div>
<header> <header>
<HeaderCanvas :project-id="1" /> <HeaderCanvas :project-id="1" />
</header> </header>
</div> </div>
<div> <div>
<h1 class="page-title">PAGE CANVAS</h1> <h1 class="page-title">PAGE CANVAS</h1>
<p class="canvas-help-text"> <p class="canvas-help-text">
Cliquez sur un champ du tableau pour afficher son contenu en détail ci-dessous. Cliquez sur un champ du tableau pour afficher son contenu en détail
</p> ci-dessous.
<LeanCanvas :is-admin=isAdmin />
<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> </p>
</div> <LeanCanvas :is-admin="isAdmin" />
<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>
<script setup lang="ts">
<script setup lang="ts">
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 /*, defineProps*/} from "vue"; import { ref, onMounted /*, defineProps*/ } from "vue";
import { axiosInstance } from "@/services/api.ts"; import { axiosInstance } from "@/services/api.ts";
const IS_MOCK_MODE = true; const IS_MOCK_MODE = true;
adnane marked this conversation as resolved Outdated
Outdated
Review

This should not be true

This should not be true

Le fetching n'est pas encore prêt pour passer à la base de données

Le fetching n'est pas encore prêt pour passer à la base de données
@ -41,86 +49,93 @@ const props = defineProps<{
is_admin = token.includes("MyINPulse-admin") is_admin = token.includes("MyINPulse-admin")
*/ */
const isAdmin = 0 const isAdmin = 0;
// Variables pour les informations de l'administrateur // Variables pour les informations de l'administrateur
const admin = ref({ const admin = ref({
idUser: 0, idUser: 0,
userSurname: "", userSurname: "",
userName: "", userName: "",
primaryMail: "", primaryMail: "",
secondaryMail: "", secondaryMail: "",
phoneNumber: "" phoneNumber: "",
}); });
const mockAdminData = { const mockAdminData = {
idUser: 1, idUser: 1,
userSurname: "ALAMI", userSurname: "ALAMI",
userName: "Adnane", userName: "Adnane",
primaryMail: "mock.admin@example.com", primaryMail: "mock.admin@example.com",
secondaryMail: "admin.backup@example.com", secondaryMail: "admin.backup@example.com",
phoneNumber: "0600000000" phoneNumber: "0600000000",
}; };
// Fonction pour récupérer les données de l'administrateur // Fonction pour récupérer les données de l'administrateur
const fetchAdminData = async (projectId: number, useMock = IS_MOCK_MODE) => { const fetchAdminData = async (projectId: number, useMock = IS_MOCK_MODE) => {
try { try {
if (useMock) { if (useMock) {
console.log("Utilisation des données mockées pour l'administrateur"); console.log(
admin.value = mockAdminData; "Utilisation des données mockées pour l'administrateur"
return; );
} admin.value = mockAdminData;
return;
}
const response = await axiosInstance.get(`/shared/projects/admin/${projectId}`); const response = await axiosInstance.get(
adnane marked this conversation as resolved Outdated
Outdated
Review

weird to use axiosInstance.

weird to use axiosInstance.
admin.value = response.data; `/shared/projects/admin/${projectId}`
} catch (error) { );
console.error("Erreur lors de la récupération des données de l'administrateur :", error); 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 // Appeler la fonction fetch au montage du composant
onMounted(() => { onMounted(() => {
const projectId = 1; const projectId = 1;
fetchAdminData(projectId); fetchAdminData(projectId);
}); });
</script> </script>
<style scoped> <style scoped>
.page-title { .page-title {
text-align: center; text-align: center;
font-size: 2.5rem; font-size: 2.5rem;
margin-top: 20px; margin-top: 20px;
} }
.canvas-help-text { .canvas-help-text {
text-align: center; text-align: center;
font-size: 0.7rem; font-size: 0.7rem;
color: #666; color: #666;
} }
.info-box { .info-box {
background-color: #f9f9f9; background-color: #f9f9f9;
padding: 15px; padding: 15px;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
width: 30%; width: 30%;
max-width: 600px; max-width: 600px;
margin: 20px auto; margin: 20px auto;
} }
.info-box p { .info-box p {
font-size: 16px; font-size: 16px;
line-height: 1.5; line-height: 1.5;
color: #333; color: #333;
} }
.info-box a { .info-box a {
color: #007bff; color: #007bff;
text-decoration: none; text-decoration: none;
} }
.info-box a:hover { .info-box a:hover {
text-decoration: underline; text-decoration: underline;
} }
</style> </style>

View File

@ -1,167 +1,162 @@
<template> <template>
<form class="add-project-form" @submit.prevent="submitForm"> <form class="add-project-form" @submit.prevent="submitForm">
<h2>Ajouter un projet</h2> <h2>Ajouter un projet</h2>
<div class="form-group"> <div class="form-group">
<label for="name">Nom du projet</label> <label for="name">Nom du projet</label>
<input <input id="name" v-model="form.name" type="text" required />
id="name" </div>
v-model="form.name"
type="text" <h3>Entrepreneur</h3>
required
/> <div class="form-group">
</div> <label for="founderName">Nom</label>
<input
<h3>Entrepreneur</h3> id="founderName"
v-model="form.founder.userName"
<div class="form-group"> type="text"
<label for="founderName">Nom</label> required
<input />
id="founderName" </div>
v-model="form.founder.userName" <div class="form-group">
type="text" <label for="founderSurname">Prénom</label>
required <input
/> id="founderSurname"
</div> v-model="form.founder.userSurname"
<div class="form-group"> type="text"
<label for="founderSurname">Prénom</label> required
<input />
id="founderSurname" </div>
v-model="form.founder.userSurname" <div class="form-group">
type="text" <label for="founderPrimaryMail">Email Principal</label>
required <input
/> id="founderPrimaryMail"
</div> v-model="form.founder.primaryMail"
<div class="form-group"> type="email"
<label for="founderPrimaryMail">Email Principal</label> required
<input />
id="founderPrimaryMail" </div>
v-model="form.founder.primaryMail" <div class="form-group">
type="email" <label for="founderSecondaryMail">Email Secondaire</label>
required <input
/> id="founderSecondaryMail"
</div> v-model="form.founder.secondaryMail"
<div class="form-group"> type="email"
<label for="founderSecondaryMail">Email Secondaire</label> />
<input </div>
id="founderSecondaryMail" <div class="form-group">
v-model="form.founder.secondaryMail" <label for="founderPhoneNumber">Numéro de téléphone</label>
type="email" <input
/> id="founderPhoneNumber"
</div> v-model="form.founder.phoneNumber"
<div class="form-group"> type="tel"
<label for="founderPhoneNumber">Numéro de téléphone</label> required
<input />
id="founderPhoneNumber" </div>
v-model="form.founder.phoneNumber" <div class="form-group">
type="tel" <label for="founderSchool">École</label>
required <input
/> id="founderSchool"
</div> v-model="form.founder.school"
<div class="form-group"> type="text"
<label for="founderSchool">École</label> required
<input />
id="founderSchool" </div>
v-model="form.founder.school" <div class="form-group">
type="text" <label for="founderCourse">Département</label>
required <input
/> id="founderCourse"
</div> v-model="form.founder.course"
<div class="form-group"> type="text"
<label for="founderCourse">Département</label> required
<input />
id="founderCourse" </div>
v-model="form.founder.course" <div class="form-group">
type="text" <label for="founderSneeStatus">Statut étudiant entrepreneur</label>
required <input
/> id="founderSneeStatus"
</div> v-model="form.founder.sneeStatus"
<div class="form-group"> type="checkbox"
<label for="founderSneeStatus">Statut étudiant entrepreneur</label> />
<input </div>
id="founderSneeStatus"
v-model="form.founder.sneeStatus" <button type="submit">Soumettre</button>
type="checkbox"
/>
</div>
<button type="submit">Soumettre</button>
</form> </form>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
import { postApi } from "@/services/api"; import { postApi } from "@/services/api";
const form = ref({ const form = ref({
name: '', name: "",
founder: { founder: {
userSurname: '', userSurname: "",
userName: '', userName: "",
primaryMail: '', primaryMail: "",
secondaryMail: '', secondaryMail: "",
phoneNumber: '', phoneNumber: "",
school: '', school: "",
course: '', course: "",
sneeStatus: false sneeStatus: false,
} },
}); });
function submitForm() { function submitForm() {
postApi("/entrepreneur/projects/request", form.value); postApi("/entrepreneur/projects/request", form.value);
} }
</script> </script>
<style scoped> <style scoped>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap'); @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap");
adnane marked this conversation as resolved
Review

this should be in a specific css file.

this should be in a specific css file.
.add-project-form { .add-project-form {
font-family: 'Inter', sans-serif; font-family: "Inter", sans-serif;
max-width: 600px; max-width: 600px;
margin: 0 auto; margin: 0 auto;
padding: 20px; padding: 20px;
background: #fff; background: #fff;
border-radius: 10px; border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
} }
adnane marked this conversation as resolved
Review

same comment, it should be gloabal

same comment, it should be gloabal
/* Le reste reste inchangé */ /* Le reste reste inchangé */
h2 { h2 {
margin-bottom: 20px; margin-bottom: 20px;
font-size: 24px; font-size: 24px;
color: #333; color: #333;
} }
h3 { h3 {
margin-top: 20px; margin-top: 20px;
font-size: 20px; font-size: 20px;
color: #555; color: #555;
} }
.form-group { .form-group {
margin-bottom: 15px; margin-bottom: 15px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
label { label {
font-weight: 500; font-weight: 500;
margin-bottom: 5px; margin-bottom: 5px;
} }
input { input {
padding: 8px; padding: 8px;
border-radius: 5px; border-radius: 5px;
border: 1px solid #ccc; border: 1px solid #ccc;
font-size: 1em; font-size: 1em;
} }
input[type="checkbox"] { input[type="checkbox"] {
width: auto; width: auto;
margin-right: 10px; margin-right: 10px;
} }
button { button {
background-color: #4caf50; background-color: #4caf50;
color: white; color: white;
padding: 10px 15px; padding: 10px 15px;
@ -171,20 +166,20 @@
font-size: 1em; font-size: 1em;
width: 100%; width: 100%;
font-weight: 500; font-weight: 500;
} }
button:hover { button:hover {
background-color: #45a049; background-color: #45a049;
} }
button:active { button:active {
background-color: #388e3c; background-color: #388e3c;
} }
input[type="text"]:focus, input[type="text"]:focus,
input[type="email"]:focus, input[type="email"]:focus,
input[type="tel"]:focus { input[type="tel"]:focus {
border-color: #4CAF50; border-color: #4caf50;
outline: none; outline: none;
} }
</style> </style>

View File

@ -15,13 +15,11 @@ import ErrorModal from "@/components/errorModal.vue";
</template> </template>
<style scoped> <style scoped>
.error-wrapper {
.error-wrapper{ position: absolute;
position: absolute; left: 70%;
left: 70%; /*background-color: blue;*/
Review

why ?

why ?
/*background-color: blue;*/ height: 100%;
height: 100%; width: 30%;
width: 30%;
} }
</style> </style>