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;
}
toObject() {
return {
idProject: this.idProject,
projectName: this.projectName,
creationDate: this.creationDate,
logo: this.logo,
status: this.status,
};
}
}
export default Project;

View File

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

View File

@ -3,18 +3,20 @@
<div class="project-header">
<h2 @click="goToLink">{{ projectName }}</h2>
<div class="header-actions">
<div class="dropdown-wrapper">
<!-- Empêche la propagation du clic vers le parent -->
<div ref="dropdownRef" class="dropdown-wrapper">
<button class="contact-button" @click.stop="toggleDropdown">
Contact
</button>
<div v-if="isDropdownOpen" class="dropdown-menu">
<button @click.stop="contactAll">Contacter tous</button>
<div
v-if="entrepreneurEmails.length > 0"
class="contact-dropdown"
:class="{ 'dropdown-visible': isDropdownOpen }"
>
<button @click="contactAll">Contacter tous</button>
<button
v-for="(email, index) in entrepreneurEmails"
:key="index"
@click.stop="contactSingle(email)"
@click="contactSingle(email)"
>
{{ email }}
</button>
@ -37,9 +39,11 @@
<script setup lang="ts">
import { defineProps, ref, onMounted, onBeforeUnmount } from "vue";
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<{
projectName: string;
@ -52,11 +56,97 @@ const router = useRouter();
const isDropdownOpen = ref(false);
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 dropdown = document.querySelector(".dropdown-wrapper");
if (dropdown && !dropdown.contains(event.target as Node)) {
if (
isDropdownOpen.value &&
dropdownRef.value &&
!dropdownRef.value.contains(event.target as Node)
) {
isDropdownOpen.value = false;
}
};
@ -69,98 +159,6 @@ onMounted(() => {
onBeforeUnmount(() => {
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>
<style scoped>
@ -233,48 +231,78 @@ const contactSingle = (email: string) => {
line-height: 1.6;
}
button {
padding: 10px 15px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
border-radius: 5px;
}
button:hover {
background-color: #0056b3;
.header {
display: flex;
adnane marked this conversation as resolved Outdated
Outdated
Review

generic style once again

generic style once again
justify-content: space-between;
align-items: center;
padding: 15px 30px;
background-color: #f9f9f9;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.dropdown-wrapper {
.logo {
height: 50px;
}
.header-actions {
display: flex;
align-items: center;
gap: 20px;
position: relative;
}
.dropdown-menu {
position: absolute;
top: 100%; /* juste en dessous du bouton */
right: 0;
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;
.contact-button,
.return-button {
background-color: #009cde;
color: white;
border: none;
padding: 10px 15px;
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 {
background-color: #f0f0f0;
.return-button:hover,
.contact-button:hover {
background-color: #007bad;
}
.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>
</div>
</div>
<RouterLink to="/" class="return-button">Retour</RouterLink>
</div>
<RouterLink to="/" class="return-button">Retour</RouterLink>
</div>
</header>
</template>

View File

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

View File

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

View File

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

View File

@ -40,6 +40,8 @@
<script setup lang="ts">
import { ref } from "vue";
import Project from "@/ApiClasses/Project";
import { requestProjectCreation } from "@/services/Apis/Entrepreneurs.ts";
const choix = ref<string | null>(null);
const nomProjet = ref("");
@ -53,8 +55,36 @@ const validerCreation = () => {
alert("Veuillez entrer un nom de projet.");
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>
<style scoped>