Ardoise

Ardoise

Ce projet de conception de jeu à été réalisé en équipe de 5 contenant 2 artistes et 3 programmeurs.

Le but de ce projet était de créer un projet permettant la gestion du temps en équipe. Cet outil permet de créer des projets, d’y ajouter des partenaires, clients ainsi que d’ajouter l’historique des tâches de chacun. Une tâche peut être créée avec sa description et être visible par les coéquipiers.

Lors de ce travail d’équipe, mes tâches étaient de :

  • Organiser mes tâches selon ma méthode « Kanban » grâce à Freedcamp.
  • Concevoir le système de notifications (reliées à une bases de donnée pour les recevoir lors de la connexion).
  • Concevoir le système de création et modification des projets.
  • Mise en place du système de connexion grâce aux services Google.
  • Permettre l’ajout de clients aux projets.

Exemples de code conçus lors de ce projet

Composant permettant la création d’un projet dans la base de donnée de Firebase.

import { addDoc, collection, query, setDoc, where } from '@firebase/firestore';
import { useEffect, useState } from 'react';
import { useHistory } from 'react-router';
import { db } from '../../../config/firebase';
import { useAuth } from '../../../context/authContext';
import Form from 'react-bootstrap/Form';
import "../../Formulaire/FormCreate.scss";
import "../../Formulaire/Formulaire.scss";
import { onSnapshot } from 'firebase/firestore';
import { useNotifs } from '../../../context/notificationsContext';
import { Link } from 'react-router-dom';


// import { doc, updateDoc, arrayUnion, arrayRemove } from "firebase/firestore";

const ProjetsCreate = () => {

    const {user} = useAuth();
    const {updateNotif, sendAjoutProjetNotifs} = useNotifs();
    
    const history = useHistory();

    const [projet,setProjet] = useState(
        {
            titreProjet: "",
            couleur : "",
            dateLivraison : "",
            participants : [],
            participantsId : [],
            idClient : "",
            client : [],
            adminId: user.id,
        }
    );
        
    // liste des participants presents dans le user
    const [partenairesListe, setPartenairesListe] = useState([]);

    useEffect(() => {
        // initie la liste des partenaires contenus dans le user
        const unsub = onSnapshot(collection(db, 'users', user.id, 'partenaires'), (snapshot) => {
            // retourne un tableau des partenaires avec toute les proprietes
            const tableau = snapshot.docs.map(
                doc => doc.data()
            );
            // s'il y a une valeur dans le tableau
            if(tableau.length > 0){
                // set la liste initiale de partenaires a sa valeur
                setPartenairesListe(tableau);
            }
        });
    },[]);

    // obtention des clients du user
    const [clients, setClients] = useState([]);
    const [currentClient, setCurrentClient] = useState({});

    const q = query(collection(db, "clients"), where("createurId", "==", user.id));
    
    useEffect(() => {
        const unsub = onSnapshot(q, (snapshot) => {
            setClients(
                snapshot.docs.map(doc => {
                    return {
                        ...doc.data(),
                        id: doc.id
                    }
                })
            );
        });
    }, []);

    // enregistre le projet
    const addProjet = async() => {

        if(projet.titreProjet === ""){
            updateNotif(`Le projet doit avoir un nom!`, true);
        }
        else if(projet.couleur === ""){
            updateNotif(`Le projet doit avoir une couleur!`, true);
        }   
        else{
            updateNotif(`Projet ${projet.titreProjet} créé!`, false);
    
            let client = getCurrentClient();
            let participantsId = getParticipantsId();
    
            const docRef = await addDoc(collection(db, 'projets'), {
                titreProjet: projet.titreProjet,
                couleur: projet.couleur,
                participants: projet.participants,
                participantsId: participantsId,
                dateLivraison: projet.dateLivraison,
                idClient: currentClient,
                client : client,
                adminId: user.id,
            }).then( (projetRef) => 
                sendAjoutProjetNotifs( {...projet, projetId : projetRef.id})
            );
    
            history.push("/projets");
        }

    };

    const getParticipantsId = () => {
        let participantsId = [];

        projet.participants.forEach(
            (participant) => {
                participantsId.push(participant.id);
            }
        );

        return participantsId;

    }

    const getCurrentClient = () => {
        let leClient = {};

        clients.forEach(client => {
            if(client.id == currentClient){
                leClient = client;
            }
        });

        console.log(leClient);

        return leClient;
    }
    
    const updateProjetHandler = (value, prop) => {
        setProjet( 
            (current) => ({
                ...current,
                [prop] : value,
            })
        )
    }

    const updateParticipantsProjet = (participantId) => {

        // filtre la liste des participants contenus dans le user pour trouver celui lié au Id reçus en parametre
        const participant = partenairesListe.filter(unPartenaire => unPartenaire.id == participantId);

        setProjet(
            (current) => (
                {
                    ...current,
                    // participant [0] puisque filter retourne un array et qu'il ne peut posseder qu'une seule valeur avec le filtre
                    participants : [...projet.participants, participant[0]]
                }
            )
        );

        console.log(participant[0]);
    }

    const verifierPartenaireAjoute = (partenaire) => {
        
        let estAjoute = false;

        projet.participants.forEach(participant => {

            if(partenaire.id === participant.id){
                estAjoute = true;
            }
        });

        return estAjoute;
    }

    const retirerParticipantAjoute = (participant) => {

        // filtre le tableau des participants pour ne garder que les participants n'etant pas celui supprime
        const nouvelArraySansLeParticipant = projet.participants.filter(unParticipant => unParticipant.id !== participant.id);
        
        // modifie la valeur du state du projet pour que le participant soit retire
        setProjet(
            (current) => (
                {
                    ...current,
                    participants : nouvelArraySansLeParticipant
                }
            )
        );

    }
        
    const submitHandler = (e) =>{
        e.preventDefault();
    }

    return(
        <div className="bgForm">
        <form className="nouveauItem" onSubmit={submitHandler} noValidate>

            <div className="titleCreationForm">
                <h2>Création d'un projet </h2>
                <Link to="/projets"><i className="material-icons">close</i></Link>
            </div>
            
            <div className="formAffichage">
 
            <div className="form-group">
            <Form.Label><label>Titre du projet : </label></Form.Label>
                <input type="text" onChange={(e) => updateProjetHandler(e.target.value,"titreProjet")} value={projet.titreProjet} className="" required></input>
            </div>
            
            <p>Choisir une couleur</p>
            <div className="choixCouleur">
                <div>
                    {/* jaune */}
                    <button onClick = {(e) => updateProjetHandler(e.target.value,"couleur")} style={projet.couleur == "#ffff00" ? {backgroundColor:"#cccc00"} : {backgroundColor:"#ffff24"}} value = "#ffff00"></button>
                </div>
                <div>
                    {/* rose */}
                    <button onClick = {(e) => updateProjetHandler(e.target.value,"couleur")} style={projet.couleur == "#ff00ff" ? {backgroundColor:"#99003d"} : {backgroundColor:"#ff00ff"}} value = "#ff00ff"></button>
                </div>
                <div>
                    {/* vert */}
                    <button onClick = {(e) => updateProjetHandler(e.target.value,"couleur")} style={projet.couleur == "#00ff00" ? {backgroundColor:"#009900"} : {backgroundColor:"#00ff00"}} value = "#00ff00"></button>
                </div>
                <div>
                    {/* rouge */}
                    <button onClick = {(e) => updateProjetHandler(e.target.value,"couleur")} style={projet.couleur == "#ff0000" ? {backgroundColor:"#b30000"} : {backgroundColor:"#ff0000"}} value = "#ff0000"></button>
                </div>
                <div>
                    {/* lightblue */}
                    <button onClick = {(e) => updateProjetHandler(e.target.value,"couleur")} style={projet.couleur == "#00ffff" ? {backgroundColor:"#00b3b3"} : {backgroundColor:"#00ffff"}} value = "#00ffff"></button>
                </div>
            </div>

            <div className="form-group">
                <label>Date de livraison : </label>
                <input type="date" onChange={(e) => updateProjetHandler(e.target.value,"dateLivraison")} value={projet.dateLivraison} className="" required></input>
            </div>

            {
                // si le user contient des id de partenaires
                partenairesListe.length > 0 ?
                    <div className="partnerAdd">
                        <select value="" onChange={ (e) => (updateParticipantsProjet(e.target.value)) }>
                            {/* Option de base pour permettre le fonctionnement du select et mettre un message au user */}
                            <option value="">- Ajouter un partenaire -</option>
                            {
                                partenairesListe.map(
                                    (partenaire) => (

                                        // si le le partenaire n'est pas deja ajoute
                                        !verifierPartenaireAjoute(partenaire) ?
                                            // donne au user l'acces a une option pour selectionner le participant
                                            <option key={partenaire.id} value={partenaire.id}>{partenaire.nom}</option>
                                        :
                                        null
                                    )
                                )
                            }
                        </select>
                    </div>
                :
                null
            }
            <p>Partenaires ajoutés :</p>
            {
                <div className="affichagePartenaires">
                   
                    {
                        projet.participants.length > 0 ?
                            projet.participants.map( 
                                (participant)=> (
                                    <div className="boutonForm" key={participant.id}>
                                        <div>{participant.nom}</div>
                                        <button onClick={ () => (retirerParticipantAjoute(participant))}><i className="material-icons">close</i></button>
                                    </div>
                                )
                            )
                        :
                        null
                    }
                </div>
            }

            {
                // si le user est lie a des clients dans la db
                clients != [] ?
                    <div className="choixClient">
                        <div>Client :</div>
                        <select onChange={ (e) => (setCurrentClient(e.target.value))} value={currentClient}>
                            <option value="">Aucun client</option>
                            {
                                clients.map(
                                    (client) => (
                                        <option key={client.nom} value={client.id}>{client.nom}</option>
                                    )
                                )
                            }
                        </select>
                    </div>
                :
                    null
            }
            </div>
            <div className="footerForm">
            <div className="btnAjouter"> 
                <button onClick={addProjet} type="submit">Créer</button>
                </div>
            </div>
        </form>
        </div>
    );
}

export default ProjetsCreate;

Composant d’affichage de notifications à partir d’un contexte.

import "./Notifications.scss";
import { useNotifs } from "../../context/notificationsContext";
import { useEffect, useState } from "react";
import { collection, onSnapshot, query, where } from "firebase/firestore";
import { db } from "../../config/firebase";

const Notifications = ({user}) => {

    const {texteNotif, closeNotif, closeAjoutProjetNotif, notifEstErreur} = useNotifs();

    const [ajoutProjetNotif, setAjoutProjetNotif] = useState([]);
    const [notifIsLoading, setNotifsIsLoading] = useState(true);

    useEffect(() => {
        const q = query(collection(db, "notifs"), where("user", "==", user.id), where("eteVu", "==", "false"));
        const unsub = onSnapshot(q, 
            (querySnapshot) => (
                setAjoutProjetNotif(
                    querySnapshot.docs.map(
                        (doc) => {
                            return {
                                ...doc.data(),
                                id : doc.id
                            }
                        }
                    )
                )
            )
        )
        setNotifsIsLoading(false);
    },[])

    if(!notifIsLoading){
        // Affiche les notifs d'ajout a un projet s'il y a une ref
        if(ajoutProjetNotif.length > 0){
            return(
                <div className="notifContainer animate__animated animate__fadeInDown">
                    <div className="notifBody">
                        <div className="notifHeader">
                            <div className="notifTriangle">O</div>
                            <h3>Notifications</h3>
                            <button href="#" onClick={ () => (closeAjoutProjetNotif(ajoutProjetNotif))}><i className="material-icons">highlight_off</i></button>
                        </div>
                        <div className="notifContent">
                            {/* <p className="notifTime">00:00</p> */}
                            <p>{`Vous avez été ajouté au projet${ajoutProjetNotif.length > 1 ? "s" : ""} suivant${ajoutProjetNotif.length > 1 ? "s" : ""} :`}</p>
                            <ul>
                                {
                                    ajoutProjetNotif.map(
                                        (projetNotif) => (
                                            <li key={projetNotif.titreProjet}>{projetNotif.titreProjet}</li>
                                        )
                                    )
                                }
                            </ul>
                        </div>
                    </div>
                </div>
            ); 
        }
        // sinon affiche les notifs en runtim de feedback
        else if(texteNotif !== "") {
            // si la notif n'est pas de type d'erreur
            if(!notifEstErreur){
                return(
                    <div className="notifContainer animate__animated animate__fadeInDown">
                        <div className="notifBody">
                            <div className="notifHeader">
                                <div className="notifTriangle">O</div>
                                <h3>Notifications</h3>
                                <button href="#" onClick={closeNotif}><i className="material-icons">highlight_off</i></button>
                            </div>
                            <div className="notifContent">
                                {/* <p className="notifTime">00:00</p> */}
                                <p>{texteNotif}</p>
                            </div>
                        </div>
                    </div>
                );    
            }
            // si la notification est de type erreur
            else{
                return(
                    <div className="notifContainer animate__animated animate__fadeInDown">
                        <div className="notifBody">
                            <div className="notifHeader">
                            <div className="notifTriangle">O</div>
                                <h3 style={{color: "red"}}>Notifications</h3>
                                <button href="#" onClick={closeNotif}><i className="material-icons">highlight_off</i></button>
                            </div>
                            <div className="notifContent">
                                {/* <p className="notifTime">00:00</p> */}
                                <p>{texteNotif}</p>
                            </div>
                        </div>
                    </div>
                ); 
            }
        }
        else{
            return null;
        }
    }
    else{
        return null;
    }
}

export default Notifications;

Logiciels utilisés
Crédits
  • Le visuel des pages à été conçu par Nicol Plante et Jacob Godbout Ouellette.
  • La programmation à été réalisée avec l’aide de Vincent Chalifoux et de Marc-Olivier Beauchesne.