front_foundation #9

Closed
mohamed_maoulainine wants to merge 181 commits from front_foundation into main
8 changed files with 217 additions and 148 deletions
Showing only changes of commit 4f90da69f3 - Show all commits

View File

@ -67,6 +67,16 @@ class Project {
) { ) {
this._status = value; this._status = value;
} }
toObject() {
return {
idProject: this.idProject,
projectName: this.projectName,
creationDate: this.creationDate,
logo: this.logo,
status: this.status,
};
}
} }
export default Project; export default Project;

View File

@ -44,7 +44,7 @@ class SectionCell {
this._modificationDate = value; this._modificationDate = value;
} }
toPlainObject() { toObject() {
return { return {
idSectionCell: this._idSectionCell, idSectionCell: this._idSectionCell,
sectionId: this._sectionId, sectionId: this._sectionId,

View File

@ -3,18 +3,20 @@
<div class="project-header"> <div class="project-header">
<h2 @click="goToLink">{{ projectName }}</h2> <h2 @click="goToLink">{{ projectName }}</h2>
<div class="header-actions"> <div class="header-actions">
<div class="dropdown-wrapper"> <div ref="dropdownRef" class="dropdown-wrapper">
<!-- Empêche la propagation du clic vers le parent -->
<button class="contact-button" @click.stop="toggleDropdown"> <button class="contact-button" @click.stop="toggleDropdown">
Contact Contact
</button> </button>
<div
<div v-if="isDropdownOpen" class="dropdown-menu"> v-if="entrepreneurEmails.length > 0"
<button @click.stop="contactAll">Contacter tous</button> class="contact-dropdown"
:class="{ 'dropdown-visible': isDropdownOpen }"
>
<button @click="contactAll">Contacter tous</button>
<button <button
v-for="(email, index) in entrepreneurEmails" v-for="(email, index) in entrepreneurEmails"
:key="index" :key="index"
@click.stop="contactSingle(email)" @click="contactSingle(email)"
> >
{{ email }} {{ email }}
</button> </button>
@ -37,9 +39,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { defineProps, ref, onMounted, onBeforeUnmount } from "vue"; import { defineProps, ref, onMounted, onBeforeUnmount } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import axios from "axios"; import { getProjectEntrepreneurs } from "@/services/Apis/Shared.ts";
adnane marked this conversation as resolved Outdated
Outdated
Review

It would be better to use an ENV variable

It would be better to use an ENV variable
import UserEntrepreneur from "@/ApiClasses/UserEntrepreneur.ts";
const IS_MOCK_MODE = true; const IS_MOCK_MODE = false;
const dropdownRef = ref<HTMLElement | null>(null);
const props = defineProps<{ const props = defineProps<{
projectName: string; projectName: string;
@ -52,11 +56,97 @@ const router = useRouter();
const isDropdownOpen = ref(false); const isDropdownOpen = ref(false);
const entrepreneurEmails = ref<string[]>([]); const entrepreneurEmails = ref<string[]>([]);
const entrepreneurs = ref<UserEntrepreneur[]>([]);
const goToLink = () => {
if (props.projectLink) {
router.push(props.projectLink);
}
};
const toggleDropdown = () => {
isDropdownOpen.value = !isDropdownOpen.value;
};
const fetchMockEntrepreneurs = () => {
const mockData = [
{
userName: "Doe",
userSurname: "John",
primaryMail: "john.doe@example.com",
},
{
userName: "Smith",
userSurname: "Anna",
primaryMail: "anna.smith@example.com",
},
{
userName: "Mock",
userSurname: "User",
primaryMail: undefined,
},
];
entrepreneurs.value = mockData.map((item) => new UserEntrepreneur(item));
entrepreneurEmails.value = entrepreneurs.value
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.
.map((e) => e.primaryMail)
.filter((mail): mail is string => !!mail);
console.log("Mock entrepreneurs chargés :", entrepreneurs.value);
};
const fetchEntrepreneurs = (projectId: number, useMock = false) => {
if (useMock) {
fetchMockEntrepreneurs();
} else {
getProjectEntrepreneurs(
projectId,
(response) => {
const rawData = response.data as Partial<UserEntrepreneur>[];
entrepreneurs.value = rawData.map(
(item) => new UserEntrepreneur(item)
);
entrepreneurEmails.value = entrepreneurs.value
.map((e) => e.primaryMail)
.filter((mail): mail is string => !!mail);
},
(error) => {
console.error(
"Erreur lors de la récupération des entrepreneurs :",
error
);
}
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
);
}
};
const contactAll = () => {
const allEmails = entrepreneurEmails.value.join(", ");
navigator.clipboard
.writeText(allEmails)
.then(() => {
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));
};
const contactSingle = (email: string) => {
navigator.clipboard
.writeText(email)
.then(() => {
alert(`Adresse copiée : ${email}`);
window.open("https://partage.bordeaux-inp.fr/", "_blank");
})
.catch((err) => console.error("Erreur lors de la copie :", err));
};
// Pour fermer le dropdown si on clique ailleurs
const handleClickOutside = (event: MouseEvent) => { const handleClickOutside = (event: MouseEvent) => {
const dropdown = document.querySelector(".dropdown-wrapper"); if (
if (dropdown && !dropdown.contains(event.target as Node)) { isDropdownOpen.value &&
dropdownRef.value &&
!dropdownRef.value.contains(event.target as Node)
) {
isDropdownOpen.value = false; isDropdownOpen.value = false;
} }
}; };
@ -69,98 +159,6 @@ onMounted(() => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
document.removeEventListener("click", handleClickOutside); document.removeEventListener("click", handleClickOutside);
}); });
const toggleDropdown = () => {
isDropdownOpen.value = !isDropdownOpen.value;
};
const goToLink = () => {
if (props.projectLink) {
router.push(props.projectLink);
}
};
const fetchEntrepreneurs = async (
projectId: number,
useMock = IS_MOCK_MODE
) => {
try {
const responseData: Entrepreneur[] = useMock
? await mockFetchEntrepreneurs(/*projectId*/)
: (
await axios.get(
`http://localhost:5000/shared/projects/entrepreneurs/${projectId}`
)
).data;
entrepreneurEmails.value = responseData.map(
(item: Entrepreneur) => item.primaryMail
);
} catch (error) {
console.error(
"Erreur lors de la récupération des entrepreneurs :",
error
);
}
};
type Entrepreneur = {
idUser: number;
userSurname: string;
userName: string;
primaryMail: string;
secondaryMail: string;
phoneNumber: string;
school: string;
course: string;
sneeStatus: boolean;
};
const mockFetchEntrepreneurs = async (/*projectId: number*/) => {
return new Promise<Entrepreneur[]>((resolve) => {
setTimeout(() => {
resolve([
{
idUser: 1,
userSurname: "Doe",
userName: "John",
primaryMail: "john.doe@example.com",
secondaryMail: "johndoe@backup.com",
phoneNumber: "612345678",
school: "ENSEIRB",
course: "Info",
sneeStatus: false,
},
{
idUser: 2,
userSurname: "Smith",
userName: "Jane",
primaryMail: "jane.smith@example.com",
secondaryMail: "janesmith@backup.com",
phoneNumber: "698765432",
school: "ENSEIRB",
course: "Info",
sneeStatus: true,
},
]);
}, 500);
});
};
const contactAll = () => {
const allEmails = entrepreneurEmails.value.join(", ");
navigator.clipboard.writeText(allEmails).then(() => {
alert("Tous les emails copiés dans le presse-papiers !");
window.open("https://partage.bordeaux-inp.fr/", "_blank");
});
};
const contactSingle = (email: string) => {
navigator.clipboard.writeText(email).then(() => {
alert(`Adresse copiée : ${email}`);
window.open("https://partage.bordeaux-inp.fr/", "_blank");
});
};
</script> </script>
<style scoped> <style scoped>
@ -233,48 +231,78 @@ const contactSingle = (email: string) => {
line-height: 1.6; line-height: 1.6;
} }
button {
padding: 10px 15px; .header {
background-color: #007bff; display: flex;
adnane marked this conversation as resolved Outdated
Outdated
Review

generic style once again

generic style once again
color: white; justify-content: space-between;
border: none; align-items: center;
cursor: pointer; padding: 15px 30px;
border-radius: 5px; background-color: #f9f9f9;
} box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
button:hover {
background-color: #0056b3;
} }
.dropdown-wrapper { .logo {
height: 50px;
}
.header-actions {
display: flex;
align-items: center;
gap: 20px;
position: relative; position: relative;
} }
.dropdown-menu { .contact-button,
position: absolute; .return-button {
top: 100%; /* juste en dessous du bouton */ background-color: #009cde;
right: 0; color: white;
background-color: white;
border: 1px solid #ccc;
padding: 0.5rem;
z-index: 1000;
display: flex;
flex-direction: column;
gap: 0.5rem;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 0.25rem;
min-width: 150px;
}
.dropdown-menu button {
text-align: left;
padding: 0.3rem 0.5rem;
background: none;
border: none; border: none;
padding: 10px 15px;
cursor: pointer; cursor: pointer;
transition: background-color 0.2s; font-size: 14px;
border-radius: 5px;
text-decoration: none;
transition: background-color 0.2s ease;
font-family: Arial, sans-serif;
} }
.dropdown-menu button:hover { .return-button:hover,
background-color: #f0f0f0; .contact-button:hover {
background-color: #007bad;
} }
</style>
.contact-dropdown {
position: absolute;
top: 100%;
left: 0;
background-color: #000;
color: white;
box-shadow: 0px 4px 8px rgba(255, 255, 255, 0.2);
border-radius: 8px;
padding: 10px;
margin-top: 5px;
z-index: 1000;
min-width: 200px;
display: none;
}
.contact-dropdown button {
display: block;
width: 100%;
padding: 5px;
text-align: left;
border: none;
background: none;
cursor: pointer;
color: white;
}
.contact-dropdown button:hover {
background-color: #009cde;
}
.contact-dropdown.dropdown-visible {
display: block;
}
</style>

View File

@ -35,8 +35,8 @@
</button> </button>
</div> </div>
</div> </div>
<RouterLink to="/" class="return-button">Retour</RouterLink>
</div> </div>
<RouterLink to="/" class="return-button">Retour</RouterLink>
</div> </div>
</header> </header>
</template> </template>

View File

@ -9,12 +9,12 @@ import {
// Entrepreneurs API // Entrepreneurs API
function requestProjectCreation( function requestProjectCreation(
projectDetails: Project, // Replace 'any' with a proper type for project details if available projectDetails: Project,
onSuccessHandler?: (response: AxiosResponse) => void, onSuccessHandler?: (response: AxiosResponse) => void,
onErrorHandler?: (error: AxiosError) => void onErrorHandler?: (error: AxiosError) => void
): void { ): void {
axiosInstance axiosInstance
.post("/entrepreneur/projects/request", projectDetails) .post("/entrepreneur/projects/request", projectDetails.toObject())
.then((response) => { .then((response) => {
if (onSuccessHandler) { if (onSuccessHandler) {
onSuccessHandler(response); onSuccessHandler(response);
@ -32,12 +32,12 @@ function requestProjectCreation(
} }
function addSectionCell( function addSectionCell(
sectionCellDetails: SectionCell, // Replace 'any' with a proper type for section cell details if available sectionCellDetails: SectionCell,
onSuccessHandler?: (response: AxiosResponse) => void, onSuccessHandler?: (response: AxiosResponse) => void,
onErrorHandler?: (error: AxiosError) => void onErrorHandler?: (error: AxiosError) => void
): void { ): void {
axiosInstance axiosInstance
.post("/entrepreneur/sectionCells", sectionCellDetails.toPlainObject()) .post("/entrepreneur/sectionCells", sectionCellDetails.toObject())
.then((response) => { .then((response) => {
if (onSuccessHandler) { if (onSuccessHandler) {
onSuccessHandler(response); onSuccessHandler(response);

View File

@ -35,6 +35,7 @@
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import Header from "../components/HeaderComponent.vue"; import Header from "../components/HeaderComponent.vue";
//import Agenda from "../components/AdminAppointments.vue";
import Agenda from "../components/AgendaComponent.vue"; import Agenda from "../components/AgendaComponent.vue";
import ProjectComp from "../components/ProjectComponent.vue"; import ProjectComp from "../components/ProjectComponent.vue";
import PendingProjectComponent from "@/components/PendingProjectComponent.vue"; import PendingProjectComponent from "@/components/PendingProjectComponent.vue";

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<header> <header>
<HeaderCanvas :project-id="1" :is-admin="isAdmin_" /> <HeaderCanvas :project-id="101" :is-admin="isAdmin_" />
</header> </header>
</div> </div>
<div> <div>
@ -10,7 +10,7 @@
Cliquez sur un champ du tableau pour afficher son contenu en détail Cliquez sur un champ du tableau pour afficher son contenu en détail
ci-dessous. ci-dessous.
</p> </p>
<LeanCanvas :project-id="1" :is-admin="isAdmin_" /> <LeanCanvas :project-id="101" :is-admin="isAdmin_" />
<div class="info-box"> <div class="info-box">
<p v-if="admin"> <p v-if="admin">
Responsable : Responsable :
@ -90,7 +90,7 @@ const fetchAdminData = (projectId: number, useMock = IS_MOCK_MODE) => {
}; };
onMounted(() => { onMounted(() => {
const projectId = 1; const projectId = 101;
fetchAdminData(projectId); fetchAdminData(projectId);
}); });
</script> </script>

View File

@ -40,6 +40,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
import Project from "@/ApiClasses/Project";
import { requestProjectCreation } from "@/services/Apis/Entrepreneurs.ts";
const choix = ref<string | null>(null); const choix = ref<string | null>(null);
const nomProjet = ref(""); const nomProjet = ref("");
@ -53,8 +55,36 @@ const validerCreation = () => {
alert("Veuillez entrer un nom de projet."); alert("Veuillez entrer un nom de projet.");
return; return;
adnane marked this conversation as resolved
Review

Just do it!

Just do it!
} }
alert(`Projet "${nomProjet.value}" créé avec succès !`);
// Obtenir la date actuelle au format YYYY-MM-DD
const today = new Date();
const yyyy = today.getFullYear();
const mm = String(today.getMonth() + 1).padStart(2, "0");
const dd = String(today.getDate()).padStart(2, "0");
const formattedDate = `${yyyy}-${mm}-${dd}`;
// Créer une instance de Project
const nouveauProjet = new Project({
projectName: nomProjet.value.trim(),
creationDate: formattedDate,
status: "PENDING",
});
// Appeler lAPI
requestProjectCreation(
nouveauProjet,
(response) => {
console.log("Projet créé :", response.data);
alert(`Projet "${nomProjet.value}" créé avec succès !`);
},
(error) => {
console.error("Erreur lors de la création du projet :", error);
alert("Une erreur est survenue lors de la création du projet.");
}
);
}; };
</script> </script>
<style scoped> <style scoped>