Fonctionnalite : Ajout de la liste des contrats

This commit is contained in:
2026-04-30 13:28:57 +02:00
parent c0cdca48fa
commit 4146563f41
264 changed files with 43484 additions and 26 deletions

0
SIRH/__init__.py Normal file
View File

16
SIRH/asgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
ASGI config for SIRH project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'SIRH.settings')
application = get_asgi_application()

BIN
SIRH/static/SIRH/PS1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

2078
SIRH/static/SIRH/icons.css Normal file

File diff suppressed because it is too large Load Diff

BIN
SIRH/static/SIRH/per1.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

BIN
SIRH/static/SIRH/user.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

View File

@@ -0,0 +1,157 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PRINCICALA</title>
<link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
<link rel="stylesheet" href="{% static 'styles.css' %}">
<link rel="stylesheet" href="{% static 'icons.css' %}">
</head>
<body>
<style>
.bg-orange-dark {
background: linear-gradient(90deg, #993d00, #b34700); /* oranges plus sombres */
color: white;
}
.sidebar .nav-link {
color: white;
font-weight: 500;
}
.sidebar .nav-link:hover {
background-color: rgba(255,255,255,0.2);
border-radius: 5px;
}
.logo {
max-width: 90px;
}
main {
margin-left: 220px;
}
.stat-card {
background: rgb(255, 255, 255);
padding: 20px;
border-radius: 8px;
box-shadow: 0px 2px 5px rgba(113, 11, 11, 0.1);
}
</style>
<div class="container-fluid">
<div class="row flex-nowrap">
<nav class="col-12 col-md-3 col-lg-2 sidebar p-3">
<div class="text-center mb-4">
<img src="{% static 'CERFIF.jpg' %}" alt="Logo" class="logo mb-5">
<h4 class="text-white">CERFIG</h4>
</div>
<ul class="nav flex-column">
<li class="nav-item mb-2">
<a href="/" class="nav-link"><i class="bi bi-speedometer2"></i> Tableau de bord</a>
</li>
<li class="nav-item mb-2">
<a href="#" class="nav-link"><i class=" bi bi-airplane"></i> Demande de congé </a>
</li>
<li class="nav-item mb-2">
<a href="#" class="nav-link"><i class="bi bi-people"></i> Gestion des Employés</a>
</li>
<li class="nav-item mb-2">
<a href="#" class="nav-link"><i class="bi bi-folder"></i> Gestion des Projets</a>
</li>
<li class="nav-item mb-2">
<a href="#" class="nav-link"><i class="bi bi-airplane"></i> Gestion des Congés</a>
</li>
<li class="nav-item mb-2">
<a href="#" class="nav-link"><i class="bi bi-graph-up"></i>Rapports et Statistiques</a>
</li>
<li class="nav-item mb-2">
<a href="#" class="nav-link"><i class="bi bi-gear"></i> Paramètres</a>
</li>
<li>
<a href="#" class="nav-link"><i class="bi bi-box-arrow-right"></i> Déconnexion</a>
</li>
</ul>
</nav>
<!-- Contenu principal -->
<main class="col px-4 py-4">
<h1 class="mb-4">Bienvenue sur le tableau de bord</h1>
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="stat-card">
<h5><i class="bi bi-airplane"></i> Congés en attente</h5>
<p class="display-6 fw-bold"> {{ nombre_conges_attente }}</p>
</div>
</div>
<div class="col-md-3">
<div class="stat-card">
<h5><i class="bi bi-people"></i> Nombre Employés </h5>
<p class="display-6 fw-bold">{{ nombre_employes }}</p>
</div>
</div>
<div class="col-md-3">
<div class="stat-card">
<h5><i class="bi bi-kanban"></i> Projets en cours</h5>
</div>
</div>
</div>
<div class="card p-3">
<h3>
<i class="bi bi-people"></i> La liste des employés en congé</h3>
<table class="table table-striped">
<thead>
<tr>
<th>Employé</th>
<th>Type de congé</th>
<th>Date de début</th>
<th>Date de fin</th>
<th>Statut</th>
</tr>
</thead>
<tbody>
{% for employe in personnes_en_conge %}
<tr>
<td>{{ employe.nom }} {{ employe.prenom }}</td>
<td>{{ employe.type }}</td>
<td>{{ employe.date_debut }}</td>
<td>{{ employe.date_fin }}</td>
<td>
<span class="badge bg-success">{{ employe.statut }} </span>
<td>
</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="text-center">Personne en conge </td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</main>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,197 @@
{% load static %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Connexion SI-RH</title>
<link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
<link rel="stylesheet" href="{% static 'styles.css' %}">
</head>
<body class="d-flex justify-content-center align-items-center min-vh-100 ">
<style>
.bg-orange-dark {
background: linear-gradient(90deg, #b35400, #cc6600);
color: white;
}
</style>
<!-- Formulaire de connexion -->
<div class="p-4 shadow-sm rounded bg-white" style="max-width: 400px; margin: auto;">
<img src="{% static 'CERFIF.jpg' %}" alt="Logo" class="logo mb-3"
style="display: block; margin-left: auto; margin-right: auto; width: 60%; max-width: 500px; height: auto;">
{% if messages %}
<!-- Modal message -->
<div class="modal fade" id="messageModal" tabindex="-1" aria-labelledby="messageModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-orange-dark
{% if messages.0.tags == 'error' %}bg-orange-dark text-white
{% elif messages.0.tags == 'success' %}bg-orange-dark text-white
{% else %}bg-orange-dark text-white{% endif %}">
<h5 class="modal-title" id="messageModalLabel">
{% if messages.0.tags == 'error' %}Erreur
{% elif messages.0.tags == 'success' %}Succès
{% else %}Information{% endif %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
{% for message in messages %}
<p>{{ message }}</p>
{% endfor %}
</div>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
var messageModal = new bootstrap.Modal(document.getElementById('messageModal'));
messageModal.show();
});
</script>
{% endif %}
<form method="POST" action="{% url 'login' %}">
{% csrf_token %}
<div class="mb-3">
<label for="username">Nom dutilisateur</label>
<input type="text" name="username" class="form-control" placeholder="Entrez votre nom dutilisateur" required>
</div>
<div class="mb-3 position-relative">
<label for="password">Mot de passe</label>
<input type="password" name="password" class="form-control" placeholder="Entrez votre mot de passe" required>
<i class="bi bi-eye toggle-password" onclick="togglePassword()"
style="position:absolute; right:10px; top:38px; cursor:pointer;"></i>
</div>
<button type="submit" class="btn btn-primary w-100">Se connecter</button>
</form>
<p class="mt-3 text-center">
<a href="#" data-bs-toggle="modal" data-bs-target="#resetPasswordModal">Mot de passe oublié ?</a>
</p>
</div>
<!-- Modale de réinitialisation -->
<div class="modal fade" id="resetPasswordModal" tabindex="-1" aria-labelledby="resetPasswordModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content p-4">
<h5 class="modal-title mb-3" id="resetPasswordModalLabel">Réinitialisation du mot de passe</h5>
<form id="resetPasswordForm">
{% csrf_token %}
<div class="mb-3">
<label for="email">Entrez votre email</label>
<input type="email" name="email" class="form-control" placeholder="Email" required>
</div>
<div id="resetMessage" class="mb-3"></div>
<button type="submit" class="btn btn-warning w-100">Envoyer le lien</button>
</form>
</div>
</div>
</div>
{% if show_politique_modal %}
<!-- Modal Politique d'utilisation -->
<div class="modal fade" id="politiqueModal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header bg-orange-dark text-white">
<h5 class="modal-title">Politique d'utilisation de SI-RH</h5>
</div>
<div class="modal-body">
<p>Veuillez lire et accepter notre politique d'utilisation avant d'accéder à votre compte.</p>
<h6>Confidentialité des données</h6>
<p>Vos informations personnelles et professionnelles sont protégées. Toute divulgation non autorisée est strictement interdite.</p>
<h6>Utilisation autorisée</h6>
<p>Lapplication est réservée à un usage professionnel. Toute utilisation à des fins personnelles ou non autorisées est prohibée.</p>
<h6>Sécurité des comptes</h6>
<p>Ne partagez jamais vos identifiants. Changez votre mot de passe régulièrement et en cas de suspicion dintrusion.</p>
<h6>Responsabilités de lutilisateur</h6>
<p>Vous êtes responsable des actions effectuées via votre compte. Signalez toute anomalie ou problème à léquipe RH ou à ladministrateur.</p>
<h6>Acceptation et conformité</h6>
<p>En cliquant sur <strong>“Jaccepte”</strong>, vous confirmez avoir lu et accepté cette politique. Le non-respect peut entraîner une suspension ou une révocation de laccès.</p>
</div>
<div class="modal-footer">
<form method="POST" action="{% url 'accepter-politique' %}" style="display:inline;">
{% csrf_token %}
<button type="submit" class="btn btn-success">J'accepte</button>
</form>
<form method="POST" action="{% url 'refuser-politique' %}" style="display:inline;">
{% csrf_token %}
<button type="submit" class="btn btn-danger">Refuser</button>
</form>
</div>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
var politiqueModal = new bootstrap.Modal(document.getElementById('politiqueModal'));
politiqueModal.show();
});
</script>
<script>
document.addEventListener("DOMContentLoaded", function() {
var politiqueModal = new bootstrap.Modal(document.getElementById('politiqueModal'));
politiqueModal.show();
});
</script>
>
<script>
document.addEventListener("DOMContentLoaded", function() {
var modal = new bootstrap.Modal(document.getElementById('politiqueModal'));
modal.show();
});
</script>
{% endif %}
<!-- Scripts -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
<script>
// Afficher / masquer le mot de passe
function togglePassword() {
const input = document.querySelector('input[name="password"]');
input.type = input.type === 'password' ? 'text' : 'password';
}
// AJAX pour réinitialisation
$('#resetPasswordForm').on('submit', function(e) {
e.preventDefault();
const email = $('input[name="email"]').val();
const csrfToken = $('[name=csrfmiddlewaretoken]').val();
$.ajax({
url: "{% url 'password_reset' %}",
type: 'POST',
data: {
'email': email,
'csrfmiddlewaretoken': csrfToken
},
success: function(response) {
$('#resetMessage').html('<div class="alert alert-success">Email envoyé ! Vérifiez votre boîte de réception.</div>');
},
error: function() {
$('#resetMessage').html('<div class="alert alert-danger">Erreur lors de lenvoi. Vérifiez lemail.</div>');
}
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,150 @@
{% load static %}
{% load roles %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PRINCICALA</title>
<link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
<link rel="stylesheet" href="{% static 'styles.css' %}">
</head>
<body>
<!-- ===================== -->
<!-- SIDEBAR RESPONSIVE -->
<!-- ===================== -->
<nav class="sidebar bg-orange-dark text-white d-none d-md-block col-md-3 col-lg-2 p-3"
style="background: linear-gradient(90deg, #993d00, #b34700); position: fixed; height: 100vh; overflow-y: auto;">
<!-- Profil utilisateur -->
<div class="text-center mb-4 d-flex flex-column align-items-center" style="margin-top:20px;">
<h6 class="fw-bold text-white mb-2">{{ user.first_name }} {{ user.last_name }}</h6>
<a href="{% url 'mon_profil' %}">
{% if user.photo %}
<img src="{{ user.photo.url }}" alt="Profil"
class="rounded-circle img-profil mx-auto"
style="width: 80px; height: 80px; object-fit: cover;">
{% else %}
<div class="rounded-circle bg-secondary d-flex justify-content-center align-items-center"
style="width: 80px; height: 80px;">
<i class="bi bi-person-circle text-white" style="font-size: 2rem;"></i>
</div>
{% endif %}
</a>
</div>
<!-- Liens du menu -->
<ul class="nav flex-column">
{% if user|has_role:"Employe" or user|has_role:"Administrateur" %}
<li class="nav-item mb-2">
<a href="{% url 'mon_profil' %}" class="nav-link text-white"><i class="bi bi-person-circle"></i> Mon profil</a>
</li>
<li class="nav-item mb-2">
<a href="{% url 'activites-projet' %}" class="nav-link text-white"><i class="bi bi-list-task"></i> Suivi des Activités</a>
</li>
<li class="nav-item mb-2">
<a href="{% url 'employe-conge' %}" class="nav-link text-white"><i class="bi bi-airplane"></i> Mes Congés</a>
</li>
<li class="nav-item mb-2">
<a href="{% url 'mes_formations' %}" class="nav-link text-white"><i class="bi bi-journal-text"></i> Mes certificats</a>
</li>
<li class="nav-item mb-2">
<a href="{% url 'salle' %}" class="nav-link text-white"><i class="bi bi-calendar-check"></i> Réserver une salle</a>
</li>
{% endif %}
{% if user|has_role:"RH" or user|has_role:"Directeur" or user|has_role:"Assistante" %}
<li class="nav-item mb-2">
<a href="{% url 'employe-index' %}" class="nav-link text-white"><i class="bi bi-speedometer2"></i> Tableau de bord</a>
</li>
{% if user|has_role:"Assistante" or user|has_role:"RH" %}
<li class="nav-item mb-2">
<a href="{% url 'activites-projet' %}" class="nav-link text-white"><i class="bi bi-list-task"></i> Suivi des Activités</a>
</li>
{% endif %}
{% if user|has_role:"Directeur" %}
<li class="nav-item mb-2">
<a href="{% url 'directeur' %}" class="nav-link text-white"><i class="bi bi-graph-up"></i> Suivi des Projets</a>
</li>
{% endif %}
<li class="nav-item mb-2">
<a href="{% url 'projet-index' %}" class="nav-link text-white"><i class="bi bi-folder"></i> Gestion des Projets</a>
</li>
<li class="nav-item mb-2">
<a href="{% url 'conge' %}" class="nav-link text-white"><i class="bi bi-airplane"></i> Congés</a>
</li>
<li class="nav-item mb-2">
<a href="{% url 'mes_formations' %}" class="nav-link text-white"><i class="bi bi-journal-text"></i> Mes certificats</a>
</li>
<li class="nav-item mb-2">
<a href="{% url 'salle' %}" class="nav-link text-white"><i class="bi bi-calendar-check"></i> Réserver une salle</a>
</li>
{% if user|has_role:"RH" or user|has_role:"Directeur" %}
<li class="nav-item mb-2">
<a href="{% url 'rapport-rh' %}" class="nav-link text-white"><i class="bi bi-graph-up"></i> Rapports et Statistiques</a>
</li>
{% endif %}
<li class="nav-item mb-2">
<a href="{% url 'mon_profil' %}" class="nav-link text-white"><i class="bi bi-person-circle"></i> Mon profil</a>
</li>
{% endif %}
{% if user|has_role:"Administrateur" %}
<li class="nav-item mb-2">
<a href="{% url 'gestion-utilisateurs' %}" class="nav-link text-white"><i class="bi bi-person-gear"></i> Gestion des Utilisateurs</a>
</li>
<li class="nav-item mb-2">
<a href="{% url 'parametres-rh' %}" class="nav-link text-white"><i class="bi bi-gear"></i> Paramètres</a>
</li>
{% endif %}
<li class="nav-item mb-2">
<a href="{% url 'deconnexion' %}" class="nav-link text-white"><i class="bi bi-box-arrow-right"></i> Déconnexion</a>
</li>
</ul>
</nav>
<!-- ===================== -->
<!-- MENU MOBILE (offcanvas) -->
<!-- ===================== -->
<!-- Bouton burger (visible uniquement sur téléphone) -->
<nav class="navbar navbar-dark bg-orange-dark d-md-none p-2" style="background: linear-gradient(90deg, #993d00, #b34700);">
<button class="btn btn-light" type="button" data-bs-toggle="offcanvas" data-bs-target="#menuMobile">
<i class="bi bi-list"></i> Menu
</button>
</nav>
<!-- Menu Offcanvas mobile -->
<div class="offcanvas offcanvas-start" tabindex="-1" id="menuMobile" style="background: linear-gradient(90deg, #993d00, #b34700); color: white;">
<div class="offcanvas-header">
<h5 class="offcanvas-title">{{ user.first_name }} {{ user.last_name }}</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvas"></button>
</div>
<div class="offcanvas-body">
<!-- Tu peux copier ici le même <ul> du menu principal -->
<ul class="nav flex-column">
<li class="nav-item mb-2">
<a href="{% url 'mon_profil' %}" class="nav-link text-white"><i class="bi bi-person-circle"></i> Mon profil</a>
</li>
<li class="nav-item mb-2">
<a href="{% url 'activites-projet' %}" class="nav-link text-white"><i class="bi bi-list-task"></i> Activités</a>
</li>
<li class="nav-item mb-2">
<a href="{% url 'deconnexion' %}" class="nav-link text-white"><i class="bi bi-box-arrow-right"></i> Déconnexion</a>
</li>
</ul>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@@ -0,0 +1,10 @@
<div class="alert alert-{{ message.tags }} d-flex align-items-center" role="alert">
{% if message.tags == 'success' %}
<i class="bi bi-check-circle-fill me-2"></i>
{% elif message.tags == 'warning' %}
<i class="bi bi-exclamation-triangle-fill me-2"></i>
{% elif message.tags == 'error' %}
<i class="bi bi-x-circle-fill me-2"></i>
{% endif %}
{{ message }}
</div>

View File

@@ -0,0 +1,384 @@
{% load static %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Profil utilisateur</title>
<!-- CSS -->
<link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
<link rel="stylesheet" href="{% static 'styles.css' %}">
<style>
.bg-orange-dark {
background: linear-gradient(90deg, #993d00, #b34700); /* oranges plus sombres */
color: white;
}
.card-body p { margin-bottom: 0.5rem; }
.main {
margin-left: 0px;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="row flex-nowrap">
<!-- Sidebar -->
{% include 'menu_principal.html' %}
<!-- Main -->
<main class="col px-2 py-4" style="margin-left: 280px;">
{% if messages %}
<!-- Modal Message Django -->
<div class="modal fade" id="messageModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-orange-dark text-white">
<h5 class="modal-title">
{% if messages.0.tags == 'error' %}
Erreur
{% elif messages.0.tags == 'success' %}
Succès
{% else %}
Information
{% endif %}
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
{% for message in messages %}
<p>{{ message }}</p>
{% endfor %}
</div>
</div>
</div>
</div>
{% endif %}
<!-- Icône Notification -->
<div class="notification-bell position-fixed top-0 end-0 m-3" data-bs-toggle="modal" data-bs-target="#modalNotifications" style="z-index:1050; cursor:pointer;">
<i class="bi bi-bell-fill fs-4"></i>
<span class="badge bg-danger">{{ notifications_contrats|length }}</span>
</div>
<!-- Modal Notifications Contrats -->
<div class="modal fade" id="modalNotifications" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-orange-dark text-white">
<h5 class="modal-title"><i class="bi bi-bell me-2"></i>Contrats proches de la fin</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
{% if notifications_contrats %}
<ul class="list-group">
{% for notif in notifications_contrats %}
<h5>Votre Contrat <strong>{{ notif.numero }}</strong> signé le <strong>{{ notif.date }}</strong>
se termine dans
<span class="badge bg-danger rounded-pill">{{ notif.jours_restants }} jours</span>
</h5>
{% endfor %}
</ul>
{% else %}
<p class="text-muted">Aucun contrat proche de la fin.</p>
{% endif %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Fermer</button>
</div>
</div>
</div>
</div>
<!-- Profil -->
<div class="container py-4">
<div class="row justify-content-center">
<div class="col-12 col-md-10 col-lg-8">
<div class="card border-0 shadow-sm">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<h4 class="mb-0 fw-bold">
<i class="bi bi-person-circle me-2"></i> Mon Profil
</h4>
</div>
<div class="card-body p-3">
<!-- Infos personnelles -->
<div class="row mb-3">
<div class="col-md-6">
<p><strong>Nom :</strong> {{ employe.last_name|default:"Non renseigné" }}</p>
<p><strong>Prénom :</strong> {{ employe.first_name|default:"Non renseigné" }}</p>
<p><strong>Matricule :</strong> {{ employe.matricule|default:"Non renseigné" }}</p>
</div>
<div class="col-md-6">
<p><strong>Département :</strong> {{ employe.departement|default:"Non renseigné" }}</p>
<p><strong>Email :</strong> {{ employe.email|default:"Non renseigné" }}</p>
<p><strong>Téléphone :</strong> {{ employe.telephone|default:"Non renseigné" }}</p>
</div>
</div>
<!-- Contrats -->
<h5 class="mt-3">Contrats</h5>
{% for contrat in contrats %}
<div class="mb-3 p-3 border rounded">
<div class="row">
<div class="col-md-6">
<p><strong>Numéro :</strong> {{ contrat.numero_contrat }}</p>
<p><strong>Type :</strong> {{ contrat.type_contrat }}</p>
<p><strong>Date début :</strong> {{ contrat.date_debut|date:"d/m/Y" }}</p>
<p><strong>Date fin :</strong> {{ contrat.date_fin|date:"d/m/Y"|default:"Non précisée" }}</p>
</div>
<div class="col-md-6">
<p><strong>Salaire mensuel :</strong>
{% if contrat.salaire_mensuel %}
{{ contrat.salaire_mensuel }} GNF
{% else %}
Non précisé
{% endif %}
</p>
<p><strong>Statut :</strong>
<span class="badge
{% if contrat.statut_auto == 'Actif' %}bg-success
{% elif contrat.statut_auto == 'Terminé' %}bg-danger
{% elif contrat.statut_auto == 'Suspendu' %}bg-warning text-dark
{% else %}bg-secondary{% endif %}">
{{ contrat.statut_auto }}
</span>
</p>
<p><strong>Solde congé :</strong> {{ contrat.solde_conge|default:"Non précisé" }} jours</p>
<p><strong>Fichier :</strong>
{% if contrat.fichier_contrat %}
<a href="{{ contrat.fichier_contrat.url }}" target="_blank">
<i class="bi bi-file-earmark-pdf me-1"></i> Télécharger
</a>
{% else %}
<span class="text-muted">Aucun fichier</span>
{% endif %}
</p>
</div>
</div>
</div>
{% empty %}
<p class="text-danger">Aucun contrat trouvé.</p>
{% endfor %}
<!-- Projets -->
<h5 class="mt-3">Projets</h5>
<ul class="list-group mb-3">
{% for a in affectations %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
{{ a.projet.nom_projet }} - {{ a.temps_affectation }}%
{% if a.projet.statut %}
- <span class="badge bg-info text-dark">{{ a.projet.statut }}</span>
{% endif %}
</div>
<div>
<button type="button" class="btn bg-orange-dark btn-sm text-white"
data-bs-toggle="modal" data-bs-target="#modalFicheProjet{{ a.projet.id }}">
<i class="bi bi-info-circle"></i> Détails
</button>
</div>
</li>
{% empty %}
<li class="list-group-item text-muted text-center">Aucun projet assigné.</li>
{% endfor %}
</ul>
</div>
<div class="card-footer bg-white d-flex justify-content-end">
<button type="button" class="btn btn-outline-secondary me-2" onclick="window.history.back()">Fermer</button>
<a href="{% url 'profil' %}" class="btn bg-orange-dark text-white">
<i class="bi bi-pencil-square me-1"></i> Renseigner les autres informations
</a>
</div>
</div>
</div>
</div>
</div>
{% for a in affectations %}
<!-- Modal Détails Projet -->
<div class="modal fade" id="modalFicheProjet{{ a.projet.id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header bg-orange-dark text-white">
<h5 class="modal-title">
<i class="bi bi-info-circle me-2"></i>Détails du projet {{ a.projet.nom_projet }}
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p><strong>Nom :</strong> {{ a.projet.nom_projet }}</p>
<p><strong>Numéro convention :</strong> {{ a.projet.numero_convention }}</p>
<p><strong>Dates :</strong> {{ a.projet.date_debut|date:"d/m/Y" }} → {{ a.projet.date_fin|date:"d/m/Y" }}</p>
<p><strong>Type :</strong> {{ a.projet.get_type_projet_display }}</p>
<p><strong>Domaine :</strong> {{ a.projet.get_domaine_recherche_display }}</p>
<p><strong>Budget :</strong> {{ a.projet.budget }} GNF</p>
<p><strong>Budget RH :</strong> {{ a.projet.budget_RH }} GNF</p>
<p><strong>Statut :</strong> <span class="badge bg-info">{{ a.projet.statut }}</span></p>
<p><strong>Description :</strong> {{ a.projet.description }}</p>
<h6 class="mt-4">Employés affectés :</h6>
<ul class="list-group mb-3">
{% for aff in a.projet.affectations.all %}
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ aff.employe }} <span class="badge bg-orange-dark">{{ aff.temps_affectation }}%</span>
</li>
{% empty %}
<li class="list-group-item text-muted">Aucun employé affecté</li>
{% endfor %}
</ul>
<h6>Bailleurs :</h6>
<ul class="list-group mb-3">
{% for f in a.projet.financements.all %}
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ f.bailleur.nom }} <span class="badge bg-success">{{ f.pourcentage }}%</span>
</li>
{% empty %}
<li class="list-group-item text-muted">Aucun bailleur enregistré</li>
{% endfor %}
</ul>
<h6 class="mt-4">Documents du projet :</h6>
<ul class="list-group">
{% for doc in a.projet.documents.all %}
<li class="list-group-item d-flex justify-content-between align-items-start">
<div>
<strong>{{ doc.get_nom_document_display }}</strong><br>
pour le projet {{ a.projet.nom_projet }}<br>
{% if doc.description %}
<small class="text-muted">{{ doc.description }}</small><br>
{% endif %}
<small class="text-muted">
{% if doc.numero %}
{{ doc.numero }}
{% endif %}
</small>
<br> <small></small>
document ajouter le {{ doc.date_ajout|date:"d/m/Y" }}</small>
{% if doc.date_validite %}
<br><small class="text-muted"> et Valide jusqu'au {{ doc.date_validite|date:"d/m/Y" }}</small>
{% endif %}
</div>
</li>
{% empty %}
<li class="list-group-item text-muted">Aucun document disponible</li>
{% endfor %}
</ul>
</div>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="bi bi-x-circle me-1"></i> Fermer
</button>
</div>
</div>
</div>
</div>
{% endfor %}
</main>
</div>
</div>
<!-- JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
// Ferme les modales ouvertes avant d'en ouvrir une autre
const modals = document.querySelectorAll('.modal');
modals.forEach(modal => {
modal.addEventListener('show.bs.modal', function () {
document.querySelectorAll('.modal.show').forEach(openModal => {
if (openModal !== modal) {
const instance = bootstrap.Modal.getInstance(openModal);
if (instance) instance.hide();
}
});
});
});
// Répare le bug d'écran figé après fermeture
document.addEventListener('hidden.bs.modal', function () {
if (!document.querySelector('.modal.show')) {
document.body.classList.remove('modal-open');
document.body.style.removeProperty('padding-right');
}
});
const forms = document.querySelectorAll('form[action*="ajouter-document"]');
forms.forEach(form => {
form.addEventListener('submit', function (e) {
const btn = form.querySelector('button[type="submit"]');
const modalEl = form.closest('.modal');
if (btn) {
btn.disabled = true;
btn.innerHTML = '<i class="bi bi-hourglass-split"></i> Enregistrement...';
}
if (modalEl) {
const modalInstance = bootstrap.Modal.getInstance(modalEl);
if (modalInstance) modalInstance.hide();
}
});
});
const messageModalEl = document.getElementById('messageModal');
if (messageModalEl) {
const messageModal = new bootstrap.Modal(messageModalEl);
messageModal.show();
}
});
</script>
</body>
</html>

View File

@@ -0,0 +1,223 @@
{% load static %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Paramètres RH</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
<link rel="stylesheet" href="{% static 'styles.css' %}">
</head>
<body>
<style>
.bg-orange-dark {
background: linear-gradient(90deg, #993d00, #b34700);
color: white;
}
</style>
<div class="container-fluid">
<div class="row flex-nowrap">
<!-- Sidebar -->
{% include 'menu_principal.html' %}
<!-- Main -->
<main class="col px-2 py-4" style="margin-left: 280px;">
{% if messages %}
<!-- Modal message -->
<div class="modal fade" id="messageModal" tabindex="-1" aria-labelledby="messageModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-orange-dark
{% if messages.0.tags == 'error' %}bg-orange-dark text-white
{% elif messages.0.tags == 'success' %}bg-orange-dark text-white
{% else %}bg-orange-dark text-white{% endif %}">
<h5 class="modal-title" id="messageModalLabel">
{% if messages.0.tags == 'error' %}Erreur
{% elif messages.0.tags == 'success' %}Succès
{% else %}Information{% endif %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
{% for message in messages %}
<p>{{ message }}</p>
{% endfor %}
</div>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
var messageModal = new bootstrap.Modal(document.getElementById('messageModal'));
messageModal.show();
});
</script>
{% endif %}
<h2 class="mb-4"><i class="bi bi-gear-fill"></i> Paramètres Administration</h2>
<div class="row">
<!-- Colonne Départements -->
<div class="col-md-6">
<div class="d-flex justify-content-between align-items-center mb-2">
<h5><i class="bi bi-building"></i> Départements</h5>
<button class="btn bg-orange-dark btn-sm" data-bs-toggle="modal" data-bs-target="#modalAjouterDepartement">
<i class="bi bi-plus-circle"></i> Ajouter
</button>
</div>
<!-- Modal Ajout -->
<div class="modal fade" id="modalAjouterDepartement" tabindex="-1">
<div class="modal-dialog">
<form method="POST">
{% csrf_token %}
<input type="hidden" name="ajouter_departement" value="1">
<div class="modal-content">
<div class="modal-header bg-orange-dark text-white">
<h5 class="modal-title">Ajouter un département</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
{{ form_departement.as_p }}
</div>
<div class="modal-footer">
<button type="submit" class="btn bg-orange-dark">Enregistrer</button>
</div>
</div>
</form>
</div>
</div>
<!-- Liste des départements -->
<ul class="list-group">
{% for d in departements %}
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ d.nom }}
<div>
<!-- Bouton détails -->
<button class="btn btn-sm btn-outline-info" data-bs-toggle="modal" data-bs-target="#modalDetailDepartement{{ d.id }}">
<i class="bi bi-eye"></i> Détails
</button>
<!-- Bouton modification -->
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#modalModifierDepartement{{ d.id }}">
<i class="bi bi-pencil-square"></i>
</button>
<form method="POST" style="display:inline;">
{% csrf_token %}
<input type="hidden" name="supprimer_departement" value="1">
<input type="hidden" name="departement_id" value="{{ d.id }}">
<button type="submit" class="btn btn-sm btn-outline-danger" onclick="return confirm('Voulez-vous vraiment supprimer ce département ?');">
<i class="bi bi-trash"></i>
</button>
</form>
</div>
</li><!-- Modal détails -->
<div class="modal fade" id="modalDetailDepartement{{ d.id }}" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-orange-dark text-white">
<h5 class="modal-title">Détails du département</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p><strong>Nom :</strong> {{ d.nom }}</p>
<p><strong>Chef :</strong> {% if d.chef %}{{ d.chef.first_name }} {{ d.chef.last_name }}{% else %}Non défini{% endif %}</p>
<!-- Liste des employés du département -->
{% for emp in d.employe_set.all %}
<p>- {{ emp.username }}</p>
{% empty %}
<p>Aucun employé associé</p>
{% endfor %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="modalModifierDepartement{{ d.id }}" tabindex="-1">
<div class="modal-dialog">
<form method="POST">
{% csrf_token %}
<input type="hidden" name="modifier_departement" value="1">
<input type="hidden" name="departement_id" value="{{ d.id }}">
<div class="modal-content">
<div class="modal-header bg-orange-dark text-white">
<h5 class="modal-title">Modifier le département</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<!-- Nom du département -->
<div class="mb-3">
<label for="nom_{{ d.id }}" class="form-label">Nom du département</label>
<input type="text" class="form-control" id="nom_{{ d.id }}" name="nom" value="{{ d.nom }}" required>
</div>
<!-- Sélection du chef -->
<div class="mb-3">
<label for="chef_{{ d.id }}" class="form-label">Chef du département</label>
<select class="form-select" id="chef_{{ d.id }}" name="chef">
<option value="">-- Aucun chef --</option>
{% for emp in employes %}
<option value="{{ emp.id }}" {% if d.chef and emp.id == d.chef.id %}selected{% endif %}>
{{ emp.first_name }} {{ emp.last_name }}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn bg-orange-dark">Modifier</button>
</div>
</div>
</form>
</div>
</div>
{% endfor %}
</ul>
</div>
<!-- Colonne Groupes -->
<div class="col-md-6">
<div class="d-flex justify-content-between align-items-center mb-2">
<h5><i class="bi bi-person-badge-fill"></i> Groupes et rôles</h5>
<button class="btn bg-orange-dark btn-sm">Ajouter</button>
</div>
<ul class="list-group">
{% for g in groupes %}
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ g.name }}
<a href="{% url 'admin:auth_group_change' g.id %}" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-pencil"></i>
</a>
</li>
{% endfor %}
</ul>
</div>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@@ -0,0 +1,205 @@
{% load static %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Tableau de Bord RH</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels"></script>
<style>
h1 { font-weight: 700; }
.card { border-radius: 15px; }
.chart-container { position: relative; height: 350px; }
table { min-width: 100%; }
/* Couleurs oranges */
.bg-orange-dark { background: linear-gradient(90deg, #b35400, #cc6600); color: white; }
.bg-orange { background: #ff9f1c; color: white; }
.bg-orange-light { background: #ffc107; color: #212529; }
/* Badges */
.badge-warning { background-color: #ffc107; color: #212529; }
.badge-success { background-color: #28a745; color: #fff; }
.badge-primary { background-color: #007bff; color: #fff; }
@media print {
body * { visibility: hidden; }
.printable-table, .printable-table * { visibility: visible; }
.printable-table { position: absolute; top: 0; left: 0; width: 100%; }
}
</style>
</head>
<body>
<div class="container-fluid py-4">
<div class="row flex-nowrap">
{% include 'menu_principal.html' %}
<main class="col px-2 py-4" style="margin-left: 280px;">
<h1 class="mb-5 text-center"><i class="bi bi-bar-chart-line-fill me-2"></i>Tableau de Bord RH</h1>
<!-- Filtres -->
<form method="GET" class="row g-3 mb-5">
<div class="col-md-4">
<select name="mois" class="form-select">
<option value="">Tous les mois</option>
{% for num, nom in mois_liste %}
<option value="{{ num }}" {% if mois_selectionne == num %}selected{% endif %}>{{ nom }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<select name="departement" class="form-select">
<option value="">Tous les départements</option>
{% for d in departements %}
<option value="{{ d.id }}" {% if departement_selectionne == d.id %}selected{% endif %}>{{ d.nom }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<button type="submit" class="btn btn-outline-primary w-100"><i class="bi bi-search"></i> Filtrer</button>
</div>
</form>
<!-- Cartes statistiques -->
<div class="row g-4 mb-5">
<div class="col-md-3"><div class="card text-center"><div class="card-body"><h6 class="fw-bold"><i class="bi bi-people me-2"></i>Total Employés</h6><h3 class="fw-bold">{{ total_employes }}</h3></div></div></div>
<div class="col-md-3"><div class="card text-center"><div class="card-body"><h6 class="fw-bold"><i class="bi bi-gender-male me-2"></i>Hommes</h6><h3 class="fw-bold">{{ hommes }}</h3></div></div></div>
<div class="col-md-3"><div class="card text-center"><div class="card-body"><h6 class="fw-bold"><i class="bi bi-gender-female me-2"></i>Femmes</h6><h3 class="fw-bold">{{ femmes }}</h3></div></div></div>
<div class="col-md-3"><div class="card text-center"><div class="card-body"><h6 class="fw-bold"><i class="bi bi-clock-history me-2"></i>Contrats expirants(2mois)</h6><h3 class="fw-bold">{{ expirants }}</h3></div></div></div>
</div>
<div class="row g-4 mb-5">
<div class="col-md-3"><div class="card text-center"><div class="card-header fw-bold"><i class="bi bi-airplane me-2"></i>Congés en attente</div><div class="card-body"><h5 class="display-6 fw-bold">{{ conges_attente }}</h5></div></div></div>
<div class="col-md-3"><div class="card text-center"><div class="card-body"><h6 class="fw-bold"><i class="bi bi-kanban me-2"></i>Projets</h6><h6>(actifs / total)</h6><h3 class="fw-bold">{{ nb_projets_actifs }} / {{ nb_projets }}</h3></div></div></div>
<div class="col-md-3"><div class="card text-center"><div class="card-header fw-bold"><i class="bi bi-clock-history me-2"></i>Âge moyen</div><div class="card-body"><h5 class="display-6 fw-bold">{{ age_moyen }} ans</h5></div></div></div>
</div>
<!-- Graphiques -->
<div class="row g-4 mb-5">
<div class="col-lg-6">
<div class="card shadow"><div class="card-header bg-orange-dark text-white"><i class="bi bi-pie-chart-fill me-2"></i> Répartition par Département</div>
<div class="card-body chart-container"><canvas id="chartDepartement"></canvas></div></div>
</div>
<div class="col-lg-6">
<div class="card shadow"><div class="card-header bg-orange-dark text-white"><i class="bi bi-pie-chart-fill me-2"></i> Répartition par genre</div>
<div class="card-body chart-container"><canvas id="chartSexe"></canvas></div></div>
</div>
</div>
<div class="row g-4 mb-6">
<div class="col-lg-6">
<div class="card shadow"><div class="card-header bg-orange-dark text-white"><i class="bi bi-bar-chart-fill me-2"></i> Projets (En cours / Terminés)</div>
<div class="card-body chart-container"><canvas id="chartProjets"></canvas></div></div>
</div>
<div class="col-lg-6">
<div class="card shadow"><div class="card-header bg-orange-dark text-white"><i class="bi bi-bar-chart-fill me-2"></i> Projets par domaine de recherche</div>
<div class="card-body chart-container"><canvas id="chartDomaine"></canvas></div></div>
</div>
</div>
</main>
</div>
</div>
<script>
const couleursOrange = ['#b35400', '#ff9f1c', '#ffc107', '#ffb84d', '#ffcc80'];
const departementLabels = {{ departement_labels|safe }};
const departementCounts = {{ departement_counts|safe }};
const sexeLabels = {{ sexe_labels|safe }};
const sexeCounts = {{ sexe_counts|safe }};
const projetLabels = {{ projet_labels|safe }};
const projetCounts = {{ projet_counts|safe }};
const ticketsLabels = ["Traités", "Non traités"];
const ticketsCounts = [{{ tickets_traite }}, {{ tickets_non_traite }}];
const domaineLabels = {{ domaine_labels|safe }};
const domaineCounts = {{ domaine_counts|safe }};
new Chart(document.getElementById("chartDepartement"), {
type: 'pie',
plugins: [ChartDataLabels],
data: {
labels: departementLabels,
datasets: [{ data: departementCounts, backgroundColor: couleursOrange }]
},
options: {
plugins: {
legend: { position: 'bottom' },
datalabels: { color: '#fff', font: { weight: 'bold', size: 14 }, formatter: v => v }
}
}
});
new Chart(document.getElementById("chartSexe"), {
type: 'doughnut',
plugins: [ChartDataLabels],
data: {
labels: sexeLabels,
datasets: [{ data: sexeCounts, backgroundColor: couleursOrange }]
},
options: {
plugins: {
legend: { position: 'bottom' },
datalabels: { color: '#fff', font: { weight: 'bold', size: 14 }, formatter: v => v }
}
}
});
new Chart(document.getElementById("chartProjets"), {
type: 'bar',
plugins: [ChartDataLabels],
data: {
labels: projetLabels,
datasets: [{ label:'Nombre de projets', data: projetCounts, backgroundColor: couleursOrange }]
},
options: {
responsive: true,
plugins: {
legend: { display: false },
datalabels: { anchor:'end', align:'top', color:'#000', font:{ weight:'bold', size:16 }, formatter:v => v }
},
scales: {
y: { beginAtZero:true, ticks:{ stepSize:1, precision:0 }, title:{ display:true, text:'Nombre de projets' } },
x: { title:{ display:true, text:'' } }
}
}
});
new Chart(document.getElementById("chartDomaine"), {
type: 'bar',
plugins: [ChartDataLabels],
data: {
labels: domaineLabels,
datasets: [{ label:'Nombre de projets', data: domaineCounts, backgroundColor: couleursOrange }]
},
options: {
responsive: true,
plugins: {
legend: { display: false },
datalabels: { anchor:'end', align:'top', color:'#000', font:{ weight:'bold', size:14 }, formatter:v => v }
},
scales: {
y: { beginAtZero:true, title:{ display:true, text:'Nombre de projets' }, ticks:{ precision:0, stepSize:1 } },
x: {
ticks:{ autoSkip:false, maxRotation:100, minRotation:30, callback: function(value){ return this.getLabelForValue(value); } }
}
}
}
});
document.getElementById('btnVoirRapport').addEventListener('click', function() {
const projetId = document.getElementById('projetId').value;
if(!projetId) return alert("Veuillez sélectionner un projet.");
fetch(`/rapports/projet/${projetId}/`)
.then(resp => { if(!resp.ok) throw new Error("Projet non trouvé"); return resp.text(); })
.then(html => { document.getElementById('contenuRapportProjet').innerHTML = html; })
.catch(err => { document.getElementById('contenuRapportProjet').innerHTML = "<p class='text-danger'>Erreur lors du chargement.</p>"; console.error(err); });
});
</script>
</body>
</html>

View File

View File

@@ -0,0 +1,33 @@
{% load static %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Mot de passe oublié</title>
<link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'styles.css' %}">
</head>
<body class="container mt-5">
<div class="p-4 shadow-sm rounded bg-white" style="max-width: 400px; margin:auto;">
<h2 class="mb-4 text-center" style="color:rgba(255, 157, 0, 0.895);">🔑 Réinitialiser le mot de passe</h2>
{% if messages %}
{% for message in messages %}
<div class="alert alert-info">{{ message }}</div>
{% endfor %}
{% endif %}
<form method="POST">
{% csrf_token %}
<div class="mb-3">
<label for="email">Adresse e-mail</label>
<input type="email" name="email" class="form-control" placeholder="Entrez votre email" required>
</div>
<button type="submit" class="btn btn-primary w-100">Envoyer le lien</button>
</form>
<p class="mt-3 text-center">
<a href="{% url 'login' %}">Retour à la connexion</a>
</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,620 @@
{% load static %}
{% load roles %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mes demandes</title>
<link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
<link rel="stylesheet" href="{% static 'styles.css' %}">
<style>
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.2); }
100% { transform: scale(1); }
}
.bg-orange-dark {
background: linear-gradient(90deg, #b35400, #cc6600);
color: white;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="row flex-nowrap">
<nav class="col-12 col-md-3 col-lg-2 sidebar p-3">
{% include 'menu_principal.html' %}
</nav>
<main class="col px-4 py-4">
<!-- Boutons Notifications -->
{% if notifications %}
<div class="d-flex justify-content-end mb-3">
<button type="button" class="btn position-relative" data-bs-toggle="modal" data-bs-target="#modalNotifications">
<i class="bi bi-bell-fill"></i> Notifications
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
{{ notifications|length }}
</span>
</button>
</div>
{% endif %}
<!-- Bouton Notifications -->
{% if notifications_employe %}
<div class="d-flex justify-content-end mb-3">
<button type="button" class="btn position-relative" data-bs-toggle="modal" data-bs-target="#modalNotifications">
<i class="bi bi-bell-fill"></i> Notifications
<span id="badgeNotifications" class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
{{ notifications_employe|length }}
</span>
</button>
</div>
{% endif %}
<!-- Modal Notifications -->
<div class="modal fade" id="modalNotifications" tabindex="-1" aria-labelledby="modalNotificationsLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalNotificationsLabel">Mes Notifications</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
</div>
<div class="modal-body">
{% if notifications_employe %}
<ul class="list-group">
{% for notif in notifications_employe %}
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ notif.message|default:"Nouvelle notification" }}
<small class="text-muted">{{ notif.date_created|date:"d/m/Y H:i" }}</small>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted">Aucune notification pour le moment.</p>
{% endif %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
</div>
</div>
</div>
</div>
<script>document.addEventListener('DOMContentLoaded', function () {
var modal = document.getElementById('modalNotifications');
modal.addEventListener('show.bs.modal', function () {
fetch("{% url 'notifications-lues' %}", {
method: 'POST',
headers: {
'X-CSRFToken': '{{ csrf_token }}',
'Content-Type': 'application/json'
},
body: JSON.stringify({})
})
.then(res => res.json())
.then(data => {
if(data.status === "ok"){
document.getElementById('badgeNotifications').style.display = 'none';
}
});
});
});
</script>
<h1 class="mb-4">La liste des demandes de congés</h1>
<!-- Cartes Statistiques -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card text-white ">
<div class="card-body">
<h6 class="card-title"><i class="bi bi-list-check me-2"></i> Total Demandes</h6>
<p class="card-text fs-4">{{ total }}</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-white ">
<div class="card-body">
<h6 class="card-title"><i class="bi bi-check-circle me-2"></i> Validés</h6>
<p class="card-text fs-4">{{ valides }}</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-white ">
<div class="card-body">
<h6 class="card-title"><i class="bi bi-x-circle me-2"></i> Refusés</h6>
<p class="card-text fs-4">{{ refuses }}</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-dark ">
<div class="card-body">
<h6 class="card-title"><i class="bi bi-hourglass-split me-2"></i> En attente</h6>
<p class="card-text fs-4">{{ attentes }}</p>
</div>
</div>
</div>
</div>
{% if messages %}
<!-- Modal message -->
<div class="modal fade" id="messageModal" tabindex="-1" aria-labelledby="messageModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-orange-dark
{% if messages.0.tags == 'error' %}bg-danger text-white
{% elif messages.0.tags == 'success' %}bg-success text-white
{% else %}bg-info text-white{% endif %}">
<h5 class="modal-title" id="messageModalLabel">
{% if messages.0.tags == 'error' %}Erreur
{% elif messages.0.tags == 'success' %}Succès
{% else %}Information{% endif %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
{% for message in messages %}
<p>{{ message }}</p>
{% endfor %}
</div>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
var messageModal = new bootstrap.Modal(document.getElementById('messageModal'));
messageModal.show();
});
</script>
{% endif %}
<!-- Bouton Nouvelle Demande -->
<div class="d-flex justify-content-between align-items-center mb-3">
<h2><i class="bi bi-people"></i> Liste</h2>
<button type="button" class="btn btn-primary mb-3" data-bs-toggle="modal" data-bs-target="#modalDemandeConge">
<i class="bi bi-calendar-plus"></i> Nouvelle demande de congé
</button>
</div>
<!-- Modal Nouvelle Demande -->
<div class="modal fade" id="modalDemandeConge" tabindex="-1" aria-labelledby="modalDemandeCongeLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="modal-header bg-orange-dark">
<h5 class="modal-title" id="modalDemandeCongeLabel">
<i class="bi bi-calendar-plus me-2"></i> Nouvelle demande de congé
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
</div>
<div class="modal-body">
<fieldset class="border p-3 rounded">
<legend class="fw-bold">Informations du congé
Il vous reste {{ employe.solde_conge }} jours de congé Annuel
</legend>
<div class="row">
<div class="col-md-6 mb-3">
{{ form.type.label_tag }} {{ form.type }}
</div>
<div class="col-md-6 mb-3">
{{ form.date_debut.label_tag }} {{ form.date_debut }}
</div>
<div class="col-md-6 mb-3">
{{ form.date_fin.label_tag }} {{ form.date_fin }}
</div>
<div class="col-md-6 mb-3">
{{ form.nombre_jours.label_tag }} {{ form.nombre_jours }}
</div>
</div>
</fieldset>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-success">
<i class="bi bi-send"></i> Envoyer
</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="bi bi-x-circle"></i> Annuler
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Tableau des demandes -->
<table class="table table-striped mt-2">
<thead>
<tr>
<th>Employe</th>
<th>Date demande</th>
<th>Type</th>
<th>Date début</th>
<th>Date fin</th>
<th>Nombre jours</th>
<th>Jours restant</th>
<th>Statut</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for conge in conges %}
<tr>
<td>{{ conge.employe.first_name }} {{ conge.employe.last_name }}</td>
<td>{{ conge.date_demande|date:"d/m/Y" }}</td>
<td>{{ conge.type }}</td>
<td>{{ conge.date_debut|date:"d/m/Y"}}</td>
<td>{{ conge.date_fin|date:"d/m/Y" }}</td>
<td>{{ conge.nombre_jours }}</td>
<td>{{ conge.employe.solde_conge }} </td>
<td>
{% if conge.statut == "Refusé" %}
<span class="badge bg-danger">Refusé</span>
{% elif conge.statut == "Validé directeur" %}
<span class="badge bg-success">Valide </span>
{% elif conge.statut == "Validé chef" %}
<span class="badge bg-warning">Approuvé </span>
{% elif conge.statut == "Refusé par chef" %}
<span class="badge bg-danger">Refusé </span>
{% elif conge.statut == "En attente" %}
<span class="badge bg-warning">En attente </span>
{% endif %}
</td>
<td>
<button class="btn btn-sm btn-info"
data-bs-toggle="modal"
data-bs-target="#detailsCongeModal{{ conge.id }}">
<i class="bi bi-eye"></i> Voir
</button>
{% if conge.statut == "En attente" and conge.employe == request.user %}
<!-- Bouton modifier -->
<button class="btn btn-sm btn-primary"
data-bs-toggle="modal"
data-bs-target="#modifierCongeModal{{ conge.id }}">
Modifier
</button>
<!-- Bouton supprimer -->
<button class="btn btn-sm btn-danger"
data-bs-toggle="modal"
data-bs-target="#supprimerCongeModal{{ conge.id }}">
Supprimer
</button>
{% endif %}
</td>
{% for conge in conges %}
<div class="modal fade" id="modifierCongeModal{{ conge.id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form method="post">
{% csrf_token %}
<input type="hidden" name="conge_id" value="{{ conge.id }}">
<div class="modal-header bg-orange-dark">
<h5 class="modal-title">Modifier la demande de congé</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-2">
<label>Type de congé</label>
<select name="type" class="form-select">
<option value="Conge Annuel" {% if conge.type == "Conge Annuel" %}selected{% endif %}>Conge Annuel</option>
<option value="Maladie" {% if conge.type == "Maladie" %}selected{% endif %}>Maladie</option>
<option value="Vacances" {% if conge.type == "Vacances" %}selected{% endif %}>Vacances</option>
<option value="Maternité" {% if conge.type == "Maternité" %}selected{% endif %}>Maternité</option>
</select>
</div>
<div class="mb-2">
<label>Nombre de jours</label>
<input type="number" name="nombre_jours" class="form-control" value="{{ conge.nombre_jours }}">
</div>
<div class="mb-2">
<label>Date début</label>
<input type="date" name="date_debut" class="form-control" value="{{ conge.date_debut|date:'Y-m-d' }}">
</div>
<div class="mb-2">
<label>Date fin</label>
<input type="date" name="date_fin" class="form-control" value="{{ conge.date_fin|date:'Y-m-d' }}">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-primary">Enregistrer</button>
</div>
</form>
</div>
</div>
</div>
{% endfor %}
{% for conge in conges %}
<div class="modal fade" id="detailsCongeModal{{ conge.id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header bg-orange-dark">
<h5 class="modal-title">Détails de la demande</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<ul class="list-group list-group-flush">
<li class="list-group-item"><strong>Employé :</strong> {{ conge.employe.first_name }} {{ conge.employe.last_name }}</li>
<li class="list-group-item"><strong>Type de congé :</strong> {{ conge.type }}</li>
<li class="list-group-item"><strong>Date début :</strong> {{ conge.date_debut|date:"d/m/Y" }}</li>
<li class="list-group-item"><strong>Date fin :</strong> {{ conge.date_fin|date:"d/m/Y" }}</li>
<li class="list-group-item"><strong>Nombre de jours :</strong> {{ conge.nombre_jours }}</li>
<li class="list-group-item"><strong>Solde restant :</strong> {{ conge.employe.solde_conge }}</li>
<li class="list-group-item">
<strong>Statut :</strong>
{% if conge.statut == "En attente" %}
<span class="badge bg-warning text-dark">{{ conge.statut }}</span>
{% elif conge.statut == "Validé directeur" %}
<span class="badge bg-success">Validé</span>
{% elif conge.statut == "Refusé" %}
<span class="badge bg-danger">Refusé</span>
{% else %}
<span class="badge bg-secondary">{{ conge.statut }}</span>
{% endif %}
</li>
<li class="list-group-item"><strong>Motif :</strong> {{ conge.motif_refus|default:"—" }}</li>
<li class="list-group-item"><strong>Date de la demande :</strong> {{ conge.date_demande|date:"d/m/Y H:i" }}</li>
</ul>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
</div>
</div>
</div>
</div>
{% endfor %}
{% for conge in conges %}
<!-- Modal supprimer -->
<div class="modal fade" id="supprimerCongeModal{{ conge.id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form method="post">
{% csrf_token %}
<input type="hidden" name="delete_id" value="{{ conge.id }}">
<div class="modal-header bg-orange-dark">
<h5 class="modal-title">Supprimer la demande</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>Voulez-vous vraiment supprimer la demande de congé du
<strong>{{ conge.date_debut|date:"d/m/Y" }}</strong> au
<strong>{{ conge.date_fin|date:"d/m/Y" }}</strong> ?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-danger">Supprimer</button>
</div>
</form>
</div>
</div>
</div>
{% endfor %}
</tr>
{% empty %}
<tr>
<td colspan="10" class="text-center text-muted">Aucune demande trouvée.</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- Pagination -->
<nav>
<ul class="pagination">
{% for page_num in conges.paginator.page_range %}
<li class="page-item {% if conges.number == page_num %}active{% endif %}">
<a class="page-link" href="?page={{ page_num }}&search={{ search }}">{{ page_num }}</a>
</li>
{% endfor %}
</ul>
</nav>
<!-- Modal Notifications Chef -->
<div class="modal fade" id="modalNotifications" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header bg-orange-dark">
<h5 class="modal-title"><i class="bi bi-bell-fill me-2"></i> Demandes de congé en attente</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
{% if notifications %}
<table class="table table-bordered table-hover">
<thead class="table-light">
<tr>
<th>Employé</th>
<th>Période</th>
<th>Jours</th>
<th>Type</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for conge in notifications %}
<tr>
<td>{{ conge.employe.first_name }} {{ conge.employe.last_name }}</td>
<td>{{ conge.date_debut|date:"d/m/Y" }} - {{ conge.date_fin|date:"d/m/Y" }}</td>
<td>{{ conge.nombre_jours }}</td>
<td>{{ conge.type }}</td>
<td class="d-flex gap-1">
<!-- Valider -->
<form method="POST" action="{% url 'valider-par-chef' conge.id %}">
{% csrf_token %}
<button type="submit" class="btn btn-success btn-sm" title="Valider">
<i class="bi bi-check-circle"></i>
</button>
</form>
<!-- Refuser -->
<button class="btn btn-danger btn-sm btn-refuser"
data-id="{{ conge.id }}"
data-nom="{{ conge.employe.first_name }} {{ conge.employe.last_name }}"
data-bs-toggle="modal"
data-bs-target="#modalRefuserGlobal">
<i class="bi bi-x-circle"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="alert alert-info">Aucune demande en attente.</div>
{% endif %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
</div>
</div>
</div>
</div>
<!-- Modal Refuser -->
<div class="modal fade" id="modalRefuserGlobal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST" id="formRefuser">
{% csrf_token %}
<div class="modal-header bg-orange-dark">
<h5 class="modal-title">Refuser la demande</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p id="texteConge"></p>
<div class="mb-3">
<label class="form-label">Motif du refus</label>
{{ form.motif_refus }}
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-danger">Refuser</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
</div>
</form>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const modal = new bootstrap.Modal(document.getElementById('modalRefuserGlobal'));
const texteConge = document.getElementById('texteConge');
const formRefuser = document.getElementById('formRefuser');
document.querySelectorAll('.btn-refuser').forEach(button => {
button.addEventListener('click', function() {
const congeId = this.getAttribute('data-id');
const nomEmploye = this.getAttribute('data-nom');
texteConge.innerHTML = `Refuser le congé de <strong>${nomEmploye}</strong> ?`;
formRefuser.action = `/refuser-par-chef/${congeId}/`; // URL dynamique vers Django
formRefuser.reset();
modal.show();
});
});
});
</script>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@@ -0,0 +1,12 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('{% static "sw.js" %}').then(function(reg) {
console.log('Service worker registered.', reg);
}).catch(function(err) {
console.warn('Service worker registration failed:', err);
});
});
}
</script>

View File

@@ -0,0 +1,31 @@
{% load static %}
{% load tags_personnaliser %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
<link href="https://unpkg.com/tabulator-tables@6.4.0/dist/css/tabulator_bootstrap5.min.css" rel="stylesheet">
{% block 'css' %}{% endblock %}
<title> {% block 'titre_page'%}{% endblock%}</title>
</head>
<body>
<div class="container-fluid">
<div class="row flex-nowrap">
{% include 'parts/menu_principal.html' %}
<div class="col-9 p-4">
{% block 'contenu' %} {% endblock %}
</div>
</div>
{% block 'modal' %} {% endblock %}
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/luxon/3.7.2/luxon.min.js"></script>
<script type="text/javascript" src="https://unpkg.com/tabulator-tables/dist/js/tabulator.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.8/js/bootstrap.min.js"></script>
{% block 'js' %} {% endblock%}
</body>
</html>

View File

@@ -0,0 +1,43 @@
{% load static %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<title>Login - SIRH</title>
</head>
<body>
<div class="container-fluid vh-100">
<div class="row">
<div class="col-6 vh-100 d-flex flex-column justify-content-center align-items-center">
<img src="{% static 'img/cerfig.jpg' %}" class="w-50">
<h5 class="text-center">Bienvenue sur les systèmes de gestion du CERFIG</h5>
</div>
<div class="col-6 vh-100 d-flex justify-content-center align-items-center">
<form method="POST" action="{% url 'login' %}" class="w-100 shadow rounded px-3 py-5">
<h2 class="text-center">Connexion</h2>
{% if messages %}
{% for message in messages %}
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %}">{{message}}</div>
{% endfor %}
{% endif %}
{% csrf_token %}
<div class="mb-3">
<label for="mail">Votre adresse email :</label>
<input type="text" name="mail" class="form-control" placeholder="Entrez votre e-mail" required>
</div>
<div class="mb-3">
<label for="mot_de_passe">Mot de passe</label>
<input type="password" name="mot_de_passe" class="form-control" placeholder="Entrez votre mot de passe" required>
<i class="bi bi-eye toggle-password" onclick="togglePassword()"
style="position:absolute; right:10px; top:38px; cursor:pointer;"></i>
</div>
<button type="submit" class="btn btn-primary w-100">Se connecter</button>
</form>
</div>
</div>
</div>
</body>
</html>

67
SIRH/urls.py Normal file
View File

@@ -0,0 +1,67 @@
"""
URL configuration for SIRH project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path
from django.conf.urls.static import static
from django.conf import settings
# from simple_sso.sso_server.server import Server
from . import views
# server_sso = Server()
urlpatterns = [
path(
'',
views.login_view,
name='index'
),
path('login/',
views.login_view,
name='login'
),
path(
'deconnexion/',
views.deconnexion_view,
name='deconnexion'
),
path(
'employé/',
include("gestion_employe.urls")
),
path(
'gestion-conge/',
include("gestion_conge.urls")
),
path(
'gestion-projet/',
include("gestion_projet.urls")
),
path(
'gestion-salle/',
include("gestion_salle.urls")
),
path(
'admin/',
admin.site.urls
),
# path(
# 'sso',
# include(server_sso.get_urls())
# )
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

37
SIRH/views.py Normal file
View File

@@ -0,0 +1,37 @@
from django.contrib.auth import authenticate, login, logout
from django.shortcuts import render, redirect
from django.contrib import messages
def login_view(request):
"""
Gère la connexion des utilisateurs avec redirection selon le rôle et
vérification de l'acceptation de la politique d'utilisation.
"""
if request.method == 'POST':
email = request.POST.get('mail')
password = request.POST.get('mot_de_passe')
if not (email and password):
messages.error(request, "Veuillez remplir tous les champs.")
return render(request, 'login.html')
user = authenticate(request, username=email, password=password)
if user is None:
messages.error(request, "Nom dutilisateur ou mot de passe incorrect.")
return render(request, 'login.html')
if not user.is_active:
messages.error(request, "Compte inactif. Contactez l'administrateur.")
return render(request, 'login.html')
login(request, user)
return redirect("gestion_conges:conge")
return render(request, 'login.html')
def deconnexion_view(request):
"""Gère la déconnexion de l'utilisateur."""
logout(request)
return redirect('login')

16
SIRH/wsgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
WSGI config for SIRH project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'SIRH.settings')
application = get_wsgi_application()

View File

3
gestion_conge/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
gestion_conge/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class GestionCongeConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'gestion_conge'

17
gestion_conge/forms.py Normal file
View File

@@ -0,0 +1,17 @@
from django import forms
from .models import Conge
class CongeForm(forms.ModelForm):
"""Formulaire de demande de congé."""
class Meta:
model = Conge
fields =['type', 'date_debut', 'date_fin']
widgets = {
'date_debut': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'date_fin': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'type': forms.Select(attrs={'class': 'form-select'}),
}
labels = {
'nombre_jours':'Nombre de jours',
}

View File

@@ -0,0 +1,30 @@
# Generated by Django 5.2.13 on 2026-04-17 12:03
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('gestion_employe', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Conge',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date_debut', models.DateField(verbose_name='Date de Début')),
('date_fin', models.DateField(verbose_name='Date de Fin')),
('type', models.CharField(choices=[('conge_annuel', 'Conge Annuel')], max_length=100, verbose_name='Type de Congé')),
('date_demande', models.DateField(auto_now_add=True, verbose_name='Date de Demande')),
('validation_hierarchique', models.BooleanField(default=None, null=True)),
('validation_direction', models.BooleanField(default=None, null=True)),
('motif_refus', models.TextField(blank=True, null=True)),
('employe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='employe', to='gestion_employe.employe')),
],
),
]

View File

36
gestion_conge/models.py Normal file
View File

@@ -0,0 +1,36 @@
import pandas as pd
from django.db import models
from gestion_employe.models import Employe
class Conge(models.Model):
"""Modèle de création des congés"""
TYPE_CHOICES = [
# ('maladie', 'Maladie'),
('conge_annuel', 'Conge Annuel'),
# ('conge_maternite', 'Conge Maternité'),
# ('conge_mariage', 'Conge Mariage'),
# ('conge_naissance', 'Conge de Naissance'),
# ('conge_deces_proche', 'Conge de décès d\'un proche'),
# ('conge_mariage_proche', 'Conge de mariage d\'un proche'),
# ('autre', 'Autre'),
]
employe = models.ForeignKey(
Employe,
on_delete=models.CASCADE,
related_name="employe"
)
date_debut = models.DateField(verbose_name='Date de Début')
date_fin = models.DateField(verbose_name='Date de Fin')
type = models.CharField(max_length=100, choices=TYPE_CHOICES, verbose_name='Type de Congé')
date_demande = models.DateField(auto_now_add=True, verbose_name="Date de Demande")
validation_hierarchique = models.BooleanField(default=None, null=True)
validation_direction = models.BooleanField(default=None, null=True)
motif_refus = models.TextField(blank=True, null=True)
@property
def nombre_jours(self):
if self.date_debut and self.date_fin:
jours = pd.bdate_range(start=self.date_debut, end=self.date_fin)
return len(jours)

View File

@@ -0,0 +1,75 @@
const bouton_enregistrer_detail = document.getElementById("bouton-enregistrer-detail-conge");
if(bouton_enregistrer_detail){
bouton_enregistrer_detail.addEventListener("click", () => {
const form = document.getElementById("form-detail-conge");
const csrftoken = new FormData(form).get("csrfmiddlewaretoken");
const actionUrl = form.action;
const id_conge = document.getElementById("id_conge").value;
const validation_hierarchique_input = document.querySelector('input[name="validation_hierarchique"]:checked');
const validation_hierarchique = validation_hierarchique_input ? validation_hierarchique_input.value : null;
const validation_direction_input = document.querySelector('input[name="validation_direction"]:checked');
const validation_direction = validation_direction_input ? validation_direction_input.value : null;
const motif_refus = document.getElementById("motif_refus").value;
fetch(actionUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": csrftoken
},
body: JSON.stringify({
id_conge,
validation_hierarchique,
validation_direction,
motif_refus
})
})
.then(response => response.json())
.then(data => {
alert(data.message);
navigation.reload();
});
})
}
if(document.getElementById("validation_hierarchique_refuse")){
document.getElementById("validation_hierarchique_refuse").addEventListener('click', function(){
if(this.checked){
alert("coucou");
document.getElementById("motif_refus_container").className="d-block form-group mt-3";
}else{
document.getElementById("motif_refus_container").className="d-none";
}
})
}
if(document.getElementById("validation_hierarchique_refuse")){
document.getElementById("validation_hierarchique_refuse").addEventListener('click', function(){
if(this.checked){
document.getElementById("motif_refus_container").className="d-block form-group mt-3";
}else{
document.getElementById("motif_refus_container").className="d-none";
}
})
}
if(document.getElementById("validation_hierarchique_valide")){
document.getElementById("validation_hierarchique_valide").addEventListener('click', function(){
if(this.checked){
document.getElementById("motif_refus_container").className="d-none";
}else{
document.getElementById("motif_refus_container").className="d-block form-group mt-3";
}
})
}
if(document.getElementById("validation_direction_valide")){
document.getElementById("validation_direction_valide").addEventListener('click', function(){
if(this.checked){
document.getElementById("motif_refus_container").className="d-block form-group mt-3";
}else{
document.getElementById("motif_refus_container").className="d-none";
}
})
}

View File

@@ -0,0 +1,71 @@
const $ = (element) => document.getElementById(element);
const url_liste_conge_attente = $("liste-demande-conges").dataset.url
const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
layout : "fitColumns",
columns: [
{"title": "Nom et Prénom", "field": "prenom_nom"},
{"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{
inputFormat:"yyyy-MM-dd",
outputFormat:"dd/MM/yy",
}},
{"title": "Date de fin", "field": "date_fin", formatter:"datetime", formatterParams:{
inputFormat:"yyyy-MM-dd",
outputFormat:"dd/MM/yy",
}},
{"title": "Type de congé", "field": "type"},
{"title": "Date de la demande", "field": "date_demande"},
{"title": "Validation par supérieur hiérarchique", "field": "validation_hierarchique", formatter:"tickCross", formatterParams :{
allowEmpty : true ,
}},
{"title": "Validation par supérieur hiérarchique", "field": "validation_direction", formatter:"tickCross", formatterParams :{
allowEmpty : true ,
}},
],
pagination: true,
paginationSize: 5
})
const bouton_demande_conges = $("bouton-demande-conge");
bouton_demande_conges.addEventListener("click", (e) => {
var modalDemandeConge = new bootstrap.Modal(document.getElementById('modalDemandeConge'));
modalDemandeConge.show();
})
tableau_liste_demande_conge.on("rowClick", function(row, rowData) {
const data = rowData.getData();
$("id_conge").value = data.id;
$("employe").value = data.prenom_nom;
$("type_conge").value = data.type;
$("date_debut").value = data.date_debut;
$("date_fin").value = data.date_fin;
$("date_demande").value = data.date_demande;
$("nombre_jours").value = data.nombre_jours;
$("solde_restant").value = data.solde_conge;
$("motif_refus").value = data.motif_refus;
if($("validation_hierarchique_valide") & $("validation_hierarchique_refuse")){
$("validation_hierarchique_valide").checked = data.validation_hierarchique === true;
$("validation_hierarchique_refuse").checked = data.validation_hierarchique === false;
}
if($("validation_direction_valide") & $("validation_direction_refuse")){
$("validation_direction_valide").checked = data.validation_direction === true;
$("validation_direction_refuse").checked = data.validation_direction === false;
}
const modal = new bootstrap.Modal(document.getElementById('detailsCongeModal'));
modal.show();
});
fetch(url_liste_conge_attente)
.then(response => response.json())
.then(data => {
if(data.success === true){
tableau_liste_demande_conge.setData(data.data)
}else{
alert(data.message)
}
})

View File

@@ -0,0 +1,51 @@
{% extends "BASE.html" %}
{% load static %}
{% block 'titre_page' %} Gestion des congés {% endblock %}
{% block 'contenu' %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %}">{{message}}</div>
{% endfor %}
{% endif %}
<div class="row d-flex justify-content-center mb-4">
<div class="col text-white bg-danger d-flex flex-column justify-content-center align-items-center border rounded p-4">
<div class="card-header fw-bold">
<i class="bi bi-x-circle me-2"></i> Congés refusés
</div>
<div class="card-body text-center">
<h5 class="fw-bold">{{ nombre_conges_refuse }}</h5>
</div>
</div>
<div class="col text-white bg-warning d-flex flex-column justify-content-center align-items-center border rounded p-4 mx-2">
<div class="card-header fw-bold">
<i class="bi bi-hourglass-split me-2"></i> Congés en attente
</div>
<div class="card-body text-center">
<h5 class="fw-bold">{{ nombre_conges_en_attente }}</h5>
</div>
</div>
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4">
<div class="card-header fw-bold">
<i class="bi bi-check-circle me-2"></i> Congés Validé
</div>
<div class="card-body text-center">
<h5 class="fw-bold">{{ nombre_conges_valide }}</h5>
</div>
</div>
<div class="d-flex justify-content-between my-4">
<h3><i class="bi bi-list-ul"></i> Liste des demandes de congé</h3>
<button class='btn btn-primary' id="bouton-demande-conge">Demande de congé</button>
</div>
<div class="table-responsive">
<div id="liste-demande-conges" data-url="{% url 'gestion_conges:liste-des-conges' %}"></div>
</div>
</div>
{% endblock %}
{% block 'modal' %}
{% include 'gestion_conge/parts/modalDemandeConge.html' %}
{% include 'gestion_conge/parts/modalDetailConge.html' %}
{% endblock %}
{% block 'js' %}
<script type="text/javascript" src="{% static 'gestion_conge/js/index.js' %}"></script>
<script type="text/javascript" src="{% static 'gestion_conge/js/detail_conges.js' %}"></script>
{% endblock %}

View File

@@ -0,0 +1,26 @@
<div class="modal fade" id="modalDemandeConge" tabindex="-1" aria-labelledby="modalDemandeCongeLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header bg-orange-dark">
<h5 class="modal-title" id="modalDemandeCongeLabel">
<i class="bi bi-calendar-plus me-2"></i> Nouvelle demande de congé
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
</div>
<div class="modal-body">
<form method="post" action="{% url 'gestion_conges:demande-conge' %}" enctype="multipart/form-data">
{% csrf_token %}
{{ formulaire_demande_conge.as_p }}
<div class="modal-footer">
<button type="submit" class="btn btn-success">
<i class="bi bi-send"></i> Soumettre
</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="bi bi-x-circle"></i> Annuler
</button>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,92 @@
<div class="modal fade" id="detailsCongeModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-orange-dark">
<h5 class="modal-title">Détails de la demande de congés</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form method="post" id="form-detail-conge" action="{% url 'gestion_conges:validation-des-conges' %}">
{% csrf_token %}
<input type="hidden" id="id_conge" value="">
<div class="form-group mb-2">
<label for="motif_refus">Employé :</label>
<input type="text" class="form-control" id="employe" value="" readonly>
</div>
<div class="form-group mb-2">
<label for="type_conge">Type de congé :</label>
<select class="form-select" id="type_conge" readonly>
{% comment %} <option value="maladie">Maladie</option> {% endcomment %}
<option value="Conge Annuel">Conge Annuel</option>
{% comment %} <option value="conge_maternite">Conge Maternité</option> {% endcomment %}
{% comment %} <option value="conge_mariage">Conge Mariage</option>
<option value="conge_naissance">Conge Naissance</option>
<option value="conge_deces_proche">Conge de décès d'un proche</option>
<option value="conge_mariage_proche">Conge de mariage d'un proche</option>
<option value="autre">Autre</option> {% endcomment %}
</select>
</div>
<div class="form-group mb-2">
<label for="date_debut">Date début :</label>
<input type="date" class="form-control" id="date_debut" value="" readonly>
</div>
<div class="form-group mb-2">
<label for="date_fin">Date fin :</label>
<input type="date" class="form-control" id="date_fin" value="" readonly>
</div>
<div class="form-group mb-2">
<label for="nombre_jours">Nombre de jours :</label>
<input type="number" class="form-control" id="nombre_jours" value="" readonly>
</div>
<div class="form-group mb-2">
<label for="solde_restant">Solde restant :</label>
<input type="number" class="form-control" id="solde_restant" value="" readonly>
</div>
<div class="form-group mb-2">
<label for="date_demande">Date de la demande :</label>
<input type="text" class="form-control" id="date_demande" value="" readonly>
</div>
{% if employe_est_il_chef or est_chef_projet %}
<hr>
<h5 class="text-center">Validation par le supérieur hiérarchique</h5>
<div class="d-flex align-items-center justify-content-center mb-2">
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="validation_hierarchique" id="validation_hierarchique_valide" value="valide">
<label class="form-check-label" for="validation_hierarchique_valide">Valide</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="validation_hierarchique" id="validation_hierarchique_refuse" value="refuse" {% if conge.validation_hierarchique == False %}checked{% endif %}>
<label class="form-check-label" for="validation_hierarchique_refuse">Refusé</label>
</div>
</div>
{% endif %}
{% if membre_de_la_direction %}
<hr>
<h5 class="text-center">Validation par le directeur</h5>
<div class="d-flex align-items-center justify-content-center mb-2">
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="validation_direction" id="validation_direction_valide" value="valide" {% if conge.validation_direction == True %}checked{% endif %}>
<label class="form-check-label" for="validation_direction_valide">Valide</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="validation_direction" id="validation_direction_refuse" value="refuse" {% if conge.validation_direction == False %}checked{% endif %}>
<label class="form-check-label" for="validation_direction_refuse">Refusé</label>
</div>
</div>
{% endif %}
<hr>
<div class="d-none form-group mt-3" id="motif_refus_container">
<label for="motif_refus">Motif de refus (si applicable) :</label>
<textarea class="form-control" id="motif_refus" rows="3"></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
{% if employe_est_il_chef or membre_de_la_direction or est_chef_projet %}
<button type="button" class="btn btn-success" id="bouton-enregistrer-detail-conge">Enregistrer</button>
{% endif %}
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,48 @@
{% for conge in conges %}
<div class="modal fade" id="modifierCongeModal{{ conge.id }}" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form method="post">
{% csrf_token %}
<input type="hidden" name="conge_id" value="{{ conge.id }}">
<div class="modal-header bg-orange-dark">
<h5 class="modal-title">Modifier la demande de congé</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-2">
<label>Type de congé</label>
<select name="type" class="form-select">
<option value="Conge Annuel" {% if conge.type == "Conge Annuel" %}selected{% endif %}>Conge Annuel</option>
<option value="Maladie" {% if conge.type == "Maladie" %}selected{% endif %}>Maladie</option>
<option value="Conge maternité" {% if conge.type == "Conge maternité" %}selected{% endif %}>Conge Maternité</option>
<option value="Conge de Mariage" {% if conge.type == "Conge de Mariage" %}selected{% endif %}>Conge Mariage</option>
<option value="conge de naissance" {% if conge.type == "conge de naissance" %}selected{% endif %}>Conge de Naissance</option>
<option value="conge de deces dun proche" {% if conge.type == "conge de deces dun proche" %}selected{% endif %}>Conge de décès d'un proche</option>
<option value="conge de mariage dun proche" {% if conge.type == "conge de mariage dun proche" %}selected{% endif %}>Conge de mariage d'un proche</option>
<option value="conge pour evenements familiaux" {% if conge.type == "conge pour evenements familiaux" %}selected{% endif %}>Conge pour événements familiaux</option>
<option value="Autre" {% if conge.type == "Autre" %}selected{% endif %}>Autre</option>
</select>
</div>
<div class="mb-2">
<label>Nombre de jours</label>
<input type="number" name="nombre_jours" class="form-control" value="{{ conge.nombre_jours }}">
</div>
<div class="mb-2">
<label>Date début</label>
<input type="date" name="date_debut" class="form-control" value="{{ conge.date_debut|date:'Y-m-d' }}">
</div>
<div class="mb-2">
<label>Date fin</label>
<input type="date" name="date_fin" class="form-control" value="{{ conge.date_fin|date:'Y-m-d' }}">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
<button type="submit" class="btn bg-orange-dark">Enregistrer</button>
</div>
</form>
</div>
</div>
</div>
{% endfor %}

View File

@@ -0,0 +1,24 @@
<div class="modal fade" id="modalRefuserGlobal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST" id="formRefuser">
{% csrf_token %}
<div class="modal-header bg-orange-dark">
<h5 class="modal-title">Refuser la demande</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p id="texteConge"></p>
<div class="mb-3">
<label class="form-label">Motif du refus</label>
{{ form.motif_refus }}
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-danger">Refuser</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
</div>
</form>
</div>
</div>
</div>

3
gestion_conge/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

27
gestion_conge/urls.py Normal file
View File

@@ -0,0 +1,27 @@
from django.urls import path
from . import views
app_name = "gestion_conges"
urlpatterns = [
path(
'',
views.index,
name='conge'
),
path(
'demande_conge/',
views.demander_conge,
name='demande-conge'
),
path(
'liste-des-conges/',
views.liste_demande_conges,
name='liste-des-conges'
),
path(
'validation-des-conges/',
views.validation_de_conge,
name='validation-des-conges'
),
]

View File

14
gestion_employe/admin.py Normal file
View File

@@ -0,0 +1,14 @@
from django.contrib import admin
from .models import Departement, Employe
@admin.register(Departement)
class DepartementAdmin(admin.ModelAdmin):
list_display = ("nom", )
@admin.register(Employe)
class EmployeAdmin(admin.ModelAdmin):
list_display = (
"user",
"matricule",
"fonction",
)

84
gestion_employe/forms.py Normal file
View File

@@ -0,0 +1,84 @@
from django import forms
from .models import Contrat, Departement, Employe,Affectation,Formation
class EmployeForm(forms.ModelForm):
"""Formulaire pour modifier les informations de profil d'un employé"""
class Meta:
model = Employe
fields = (
'adresse',
'telephone',
'CV',
'diplome',
'rib',
'photo',
'casier_judiciaire'
)
class AffectationForm(forms.ModelForm):
class Meta:
model = Affectation
fields = (
'projet',
'date_affectation',
'date_fin_daffectation',
'role',
'pourcentage_temps_affectation'
)
widgets = {
'projet': forms.Select(attrs={'class': 'form-select'}),
'date_affectation': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'date_fin_daffectation': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'role': forms.Select(attrs={'class': 'form-select'}),
'pourcentage_temps_affectation': forms.NumberInput(attrs={'class': 'form-control'}),
}
class ContratForm(forms.ModelForm):
class Meta:
model = Contrat
fields = [
"numero_contrat",
"type_contrat",
"date_debut",
"date_fin",
"salaire_mensuel",
"statut",
"fichier_contrat"
]
widgets = {
'numero_contrat': forms.TextInput(attrs={'class': 'form-control'}),
'type_contrat': forms.Select(attrs={'class': 'form-select'}),
'date_debut': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'date_fin': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'salaire_mensuel': forms.TextInput(attrs={'class': 'form-control'}),
'statut': forms.Select(attrs={'class': 'form-select'}),
'fichier_contrat': forms.FileInput(attrs={'class': 'form-control'}),
}
class DepartementForm(forms.ModelForm):
class Meta:
model = Departement
fields = ['nom']
class FormationForm(forms.ModelForm):
class Meta:
model = Formation
fields = [
'titre',
'organisme',
'date_obtention',
'date_fin',
'description',
'certificat'
]
widgets = {
'titre': forms.TextInput(attrs={"class": "form-control"}),
'organisme': forms.TextInput(attrs={"class": "form-control"}),
'date_obtention': forms.DateInput(attrs={"class": "form-control", "type": "date"}),
'date_fin': forms.DateInput(attrs={"class": "form-control", "type": "date"}),
'description': forms.Textarea(attrs={"class": "form-control"}),
'certificat': forms.FileInput(attrs={"class": "form-control"}),
}

View File

@@ -0,0 +1,88 @@
# Generated by Django 5.2.13 on 2026-04-17 12:03
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('gestion_projet', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Departement',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nom', models.CharField(max_length=100)),
],
),
migrations.CreateModel(
name='Employe',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('matricule', models.CharField(blank=True, max_length=10, null=True, unique=True)),
('fonction', models.CharField(blank=True, choices=[('directeur', 'Directeur'), ('assistant_direction', 'Assistante de direction'), ('comptable', 'Comptable'), ('raf', 'RAF'), ('data_manager', 'Data Manager'), ('logisticien', 'Logisticien'), ('post_doctorant', 'Post-Doctorant'), ('qualiticien', 'Qualiticien'), ('technicien_surface', 'Technicien de surface'), ('chauffeur', 'Chauffeur')], max_length=50, null=True)),
('date_embauche', models.DateField(blank=True, null=True)),
('adresse', models.CharField(blank=True, max_length=100, null=True)),
('telephone', models.CharField(blank=True, max_length=15, null=True)),
('sexe', models.CharField(blank=True, choices=[('m', 'Masculin'), ('f', 'Féminin')], max_length=1, null=True)),
('date_naissance', models.DateField(blank=True, null=True)),
('CV', models.FileField(blank=True, null=True, upload_to='cv/')),
('diplome', models.FileField(blank=True, null=True, upload_to='diplomes/')),
('rib', models.FileField(blank=True, null=True, upload_to='rib/')),
('photo', models.ImageField(blank=True, null=True, upload_to='photos/')),
('casier_judiciaire', models.FileField(blank=True, null=True, upload_to='casier/')),
('chef', models.BooleanField(default=False, verbose_name='Cet utilisateur est-il chef de ce département ?')),
('departement', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='gestion_employe.departement')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Contrat',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('numero_contrat', models.CharField(max_length=100, unique=True)),
('type_contrat', models.CharField(choices=[('contrat_duree_determinee', 'Contrat à Durée Déterminée'), ('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'), ('contrat_prestation', 'Contrat de Prestation de Service'), ('contrat_stage', 'Contrat de Stage')], max_length=50)),
('date_debut', models.DateField()),
('date_fin', models.DateField(blank=True, null=True)),
('salaire_mensuel', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
('statut', models.CharField(choices=[('actif', 'Actif'), ('termine', 'Terminé'), ('suspendu', 'Suspendu'), ('rupture_contrat', 'Rupture de Contrat')], max_length=50)),
('fichier_contrat', models.FileField(blank=True, null=True, upload_to='contrats/')),
('employe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_employe.employe')),
],
),
migrations.CreateModel(
name='Formation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('titre', models.CharField(max_length=255, verbose_name='Nom du certificat')),
('organisme', models.CharField(max_length=255, verbose_name="Nom de l'organisme")),
('description', models.TextField(blank=True, null=True, verbose_name='Description de la formation')),
('date_obtention', models.DateField(blank=True, null=True, verbose_name="Date d'obtention")),
('date_fin', models.DateField(blank=True, null=True, verbose_name='Date de fin de validité')),
('certificat', models.FileField(blank=True, null=True, upload_to='documents/formations/', verbose_name='Certificat (PDF/Image)')),
('employe', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='employe_formation', to='gestion_employe.employe')),
],
),
migrations.CreateModel(
name='Affectation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date_affectation', models.DateField()),
('date_fin_daffectation', models.DateField(blank=True, null=True)),
('role', models.CharField(choices=[('chef_projet', 'Chef de projet'), ('doctorant', 'Doctorant'), ('mastorant', 'Mastorant'), ('consultant', 'Consultant'), ('stagiaire', 'Stagiaire'), ('laborantin', 'Laborantin'), ('medecin', 'Médecin'), ('autre', 'Autre')], default='Membre', max_length=20)),
('pourcentage_temps_affectation', models.DecimalField(decimal_places=2, max_digits=5)),
('projet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_projet.projet')),
('employe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_employe.employe')),
],
options={
'unique_together': {('projet', 'employe')},
},
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 6.0.4 on 2026-04-29 10:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('gestion_employe', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='contrat',
name='type_contrat',
field=models.CharField(choices=[('contrat_duree_determinee', 'Contrat à Durée Déterminée'), ('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'), ('contrat_prestation', 'Contrat de Prestation de Service'), ('contrat_stage', 'Contrat de Stage'), ('convention_bourse_entretien', "Convention de bourse d'entretien")], max_length=50),
),
]

View File

View File

@@ -0,0 +1,263 @@
const $ = (element) => document.getElementById(element);
const url_liste_employe = $("tableau_liste_employe").dataset.url;
const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
columns: [
{"title": "Matricule", "field": "matricule"},
{"title": "Nom & Prénom", "field": "employe"},
{"title": "Fonction", "field": "fonction"},
{"title": "Projet", "field": "projet"},
{"title": "Téléphone", "field": "telephone"},
],
// ajaxURL: url_liste_employe,
pagination: true,
paginationSize: 10,
})
fetch(url_liste_employe)
.then(response => response.json())
.then(data => {
if (data.success){
console.log(data.data)
tableau_liste_employe.setData(data.data);
}else{
alert(data.message);
}
}
)
tableau_liste_employe.on("rowClick", function (row, rowData) {
const data = rowData.getData();
document.getElementById('detail-id').value = data.id;
document.getElementById('detail-matricule').value = data.matricule;
document.getElementById('detail-employe').value = data.employe;
document.getElementById('detail-fonction').value = data.fonction;
document.getElementById('detail-departement').value = data.departement;
document.getElementById('detail-sexe').value = "";
document.getElementById('detail-dateNaissance').value = "";
document.getElementById('detail-email').value = data.email;
document.getElementById('detail-telephone').value = data.telephone;
document.getElementById('detail-adresse').value = data.adresse;
document.getElementById('detail-dateEmbauche').value = data.date_embauche;
document.getElementById('detail-dateNaissance').value = data.date_naissance;
document.getElementById('detail-sexe').value = data.sexe;
document.getElementById('document-diplome').href = data.diplome;
document.getElementById('document-diplome').textContent = data.diplome || "Aucun diplôme";
document.getElementById('document-cv').href = data.CV;
document.getElementById('document-cv').textContent = data.CV || "Aucun CV";
document.getElementById('document-rib').href = data.rib;
document.getElementById('document-rib').textContent = data.rib || "Aucun RIB";
document.getElementById('document-casier-judiciaire').href = data.casier_judiciaire;
document.getElementById('document-casier-judiciaire').textContent = data.casier_judiciaire || "Aucun casier judiciaire";
document.getElementById("document-employe").innerHTML = data.employe || "Employé inconnu";
document.getElementById("formations-list").innerHTML = "";
for (const formation of data.formations) {
document.getElementById("formations-list").innerHTML += `
<div class="col-6">
<div class="form-group mb-2">
<label>Titre :</label>
<input type="text" class="form-control" value="${formation.titre}">
</div>
<div class="form-group mb-2">
<label>Organisme :</label>
<input type="text" class="form-control" value="${formation.organisme}">
</div>
<div class="form-group mb-2">
<label>Date d'Optention :</label>
<input type="text" class="form-control" value="${formation.date_obtention}">
</div>
<div class="form-group mb-2">
<label>Date de validitée :</label>
<input type="text" class="form-control" value="${formation.date_fin}">
</div>
</div>
`
}
document.getElementById("contrat-employe").innerHTML = data.employe || "Employé inconnu";
document.getElementById("employeIdInput").value = data.id;
document.getElementById("contrats-list").innerHTML = "";
for (const contrat of data.contrats) {
document.getElementById("contrats-list").innerHTML += `
<div class="col-6">
<div class="form-group mb-2">
<label>Numéro de contrat :</label>
<input type="text" class="form-control" value="${contrat.numero_contrat}">
</div>
<div class="form-group mb-2">
<label>Type de contrat :</label>
<select class="form-select">
<option value="">Sélectionnez un projet</option>
<option value="contrat_duree_determinee" ${contrat.type_contrat == 'contrat_duree_determinee' ? 'selected' : ''}>Contrat à Durée Déterminée</option>
<option value="contrat_duree_indeterminee" ${contrat.type_contrat == 'contrat_duree_indeterminee' ? 'selected' : ''}>Contrat à Durée Indéterminée</option>
<option value="contrat_prestation" ${contrat.type_contrat == 'contrat_prestation' ? 'selected' : ''}>Contrat de Prestation de Service</option>
<option value="contrat_stage" ${contrat.type_contrat == 'contrat_stage' ? 'selected' : ''}>Contrat de Stage</option>
</select>
</div>
<div class="form-group mb-2">
<label>Date de début :</label>
<input type="text" class="form-control" value="${contrat.date_debut}">
</div>
<div class="form-group mb-2">
<label>Date de fin :</label>
<input type="text" class="form-control" value="${contrat.date_fin}">
</div>
<div class="form-group mb-2">
<label>Salaire mensuel :</label>
<input type="text" class="form-control" value="${contrat.salaire_mensuel}">
</div>
<div class="form-group mb-2">
<label>Statut :</label>
<input type="text" class="form-control" value="${contrat.statut}">
</div>
<div class="form-group mb-2">
<label>Fichier de contrat :</label>
<a href="${contrat.fichier_contrat}" target="_blank">Voir le document</a>
</div>
<button class='d-block m-auto btn btn-danger btn-supprimer-contrat' data-contratid="${contrat.numero_contrat}">
<i class='bi bi-trash'></i> Supprimer le contrat
</button>
</div>
`
};
const supprimerButtons = document.getElementsByClassName("btn-supprimer-contrat");
Array.from(supprimerButtons).forEach(button => {
button.addEventListener("click", function() {
const contratId = this.dataset.contratid;
fetch(`contrat/supprimer/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]').value
},
body: JSON.stringify({
"id": contratId
})
})
.then(response => response.json())
.then(data => {
alert(data.message);
location.reload();
})
});
})
document.getElementById("affectation-nom-employe").textContent = data.employe || "Employé inconnu";
document.getElementById("affecter_employe_id").value = data.id;
document.getElementById("affectations-list").innerHTML = "";
for (const affectation of data.affectations) {
document.getElementById("affectations-list").innerHTML += `
<div class="col-6">
<div class="form-group mb-2">
<label>Nom du projet :</label>
<input type="text" class="form-control" value="${affectation.projet}">
</div>
<div class="form-group mb-2">
<label>Rôle :</label>
<input type="text" class="form-control" value="${affectation.role}">
</div>
<div class="form-group mb-2">
<label>Pourcentation d'affectation :</label>
<input type="text" class="form-control" value="${affectation.pourcentage_temps_affectation}">
</div>
<div class="form-group mb-2">
<label>Date d'affectation :</label>
<input type="date" class="form-control" value="${affectation.date_affectation}">
</div>
<div class="form-group mb-2">
<label>Date de fin d'affectation :</label>
<input type="date" class="form-control" value="${affectation.date_fin_daffectation}">
</div>
<button class='d-block m-auto btn btn-danger btn-supprimer-affectation' data-contratid="${affectation.id}">
<i class='bi bi-trash'></i> Supprimer l'affectation
</button>
</div>
`
}
Array.from(document.getElementsByClassName("btn-supprimer-affectation")).forEach(button => {
button.addEventListener("click", function() {
const affectationId = this.dataset.contratid;
fetch(`affectation/supprimer/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]').value
},
body: JSON.stringify({
"id": affectationId
})
})
.then(response => response.json())
.then(data => {
alert(data.message);
location.reload();
})
});
})
const modal = new bootstrap.Modal($("modalDetailEmploye"));
modal.show();
})
$("enregistrerDetail").addEventListener("click", function() {
const id_ = document.getElementById('detail-id').value;
const fonction = document.getElementById('detail-fonction').value;
const dateEmbauche = document.getElementById('detail-dateEmbauche').value;
const matricule = document.getElementById('detail-matricule').value;
const url_enregistrer_detail = $("modalDetailEmployeBody").dataset.url;
fetch(url_enregistrer_detail, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]').value
},
body: JSON.stringify({
"id": id_,
"fonction": fonction,
"date_embauche": dateEmbauche,
"matricule": matricule
})
})
.then(response => response.json())
.then(data => {
alert(data.message);
location.reload();
})
});
$("input-recherche").addEventListener("input", function() {
const recherche = this.value;
if (recherche){
tableau_liste_employe.setFilter("employe", "like", recherche);
}else{
tableau_liste_employe.clearFilter();
}
})
const listeContratExpirant = new Tabulator("#listeContratExpirant", {
columns: [
{"title": "Employé", "field": "employe"},
{"title": "Type de contrat", "field": "type_contrat"},
{"title": "Date de début", "field": "date_debut", formatter: 'datetime', formatterParams:{
inputFormat:"yyyy-MM-dd",
outputFormat:"dd/MM/yy",
}},
{"title": "Date de fin", "field": "date_fin", formatter: 'datetime', formatterParams:{
inputFormat:"yyyy-MM-dd",
outputFormat:"dd/MM/yy",
}},
{"title": "Statut", "field": "statut"},
{"title": "Lien du fichier", "field": "fichier_contrat", formatter:"link", formatterParams:{
target:"_blank",
}
},
],
ajaxURL: $("boutonContratExpirants").dataset.urlexpirants,
})

View File

@@ -0,0 +1,46 @@
const $ = (element) => document.getElementById(element);
url_certificat = $("tableau-certificat").dataset.url;
const tableau_certificat = new Tabulator("#tableau-certificat", {
columns: [
{"title": "Nom du certificat", "field": "titre"},
{"title": "Nom de l'organisme", "field": "organisme"},
{"title": "Date d'obtention", "field": "date_obtention", formatter:"datetime", formatterParams:{
inputFormat:"yyyy-MM-dd",
outputFormat:"dd/MM/yy",
}},
{"title": "Date de fin de validité", "field": "date_fin", formatter:"datetime", formatterParams:{
inputFormat:"yyyy-MM-dd",
outputFormat:"dd/MM/yy",
}},
],
ajaxURL: url_certificat,
})
const enregistrerProfil = $("enregistrerProfil");
enregistrerProfil.addEventListener("click", (e) => {
const url = $("information-personnelles").dataset.url;
const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
const formData = new FormData();
formData.append("nom", $("nom").value);
formData.append("prenom", $("prenom").value);
formData.append("email", $("email").value);
formData.append("telephone", $("telephone").value);
formData.append("adresse", $("adresse").value);
formData.append("sexe", $("sexe").value);
formData.append("date_naissance", $("date_naissance").value);
fetch(url, {
method: "POST",
headers: {
"X-CSRFToken": csrftoken
},
body: formData
})
.then(response => response.json())
.then(data => alert(data.message))
.catch(error => console.error("Erreur:", error));
});

View File

@@ -0,0 +1,67 @@
{% extends "BASE.html" %}
{% load static %}
{% block 'titre_page' %} Gestion des projets {% endblock %}
{% block 'css' %}
{% endblock %}
{% block 'contenu' %}
<div class="col px-2">
<h3>Gestion des employés</h3>
{% if messages %}
{% for message in messages %}
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
<div class="row">
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4">
<span class="fs-5">Nombre d'employés</span>
<h3>{{ nombre_employes }}</h3>
</div>
<div class="col text-white bg-info d-flex flex-column justify-content-center align-items-center border rounded p-4 mx-3">
<span class="fs-5">Nombre de prestataires</span>
<h3>{{ nombre_cps }}</h3>
</div>
<div class="col text-white bg-warning d-flex flex-column justify-content-center align-items-center border rounded p-4">
<span class="fs-5">Nombre de stagiaires</span>
<h3>{{ nombre_stage }}</h3>
</div>
</div>
<div class="row mt-4">
<div class="col d-flex justify-content-between mb-3">
<h3>
<i class="bi bi-people"></i>
La liste des employés
</h3>
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#modalListeDesContratExpirants" id="boutonContratExpirants" data-urlexpirants="{% url 'gestion_employe:liste-contrat-expirants' %}">
Contrats proches de la fin <span class="badge badge-light">{{ nombre_expirants }}</span>
</button>
</div>
<div class="row mb-2 d-flex justify-content-end">
<div class="col">
<div class="row">
<div class="col d-flex justify-content-center">
<input class="form-control d-block" type="search" placeholder="Recherche par nom et prénom..." id="input-recherche">
</div>
</div>
</div>
</div>
<div class="table-responsive">
<div id="tableau_liste_employe" data-url={% url 'gestion_employe:liste-employes' %}></div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block 'modal' %}
{% include "gestion_employe/parts/modalDetailEmploye.html" %}
{% include "gestion_employe/parts/modalCreationContrat.html" %}
{% include "gestion_employe/parts/modalAffectationProjet.html" %}
{% include "gestion_employe/parts/modalDocument.html" %}
{% include "gestion_employe/parts/modalListeContratExpirants.html" %}
{% endblock %}
{% block 'js' %}
<script type="text/javascript" src="{% static 'gestion_employe/js/index.js' %}"></script>
{% endblock %}

View File

@@ -0,0 +1,105 @@
{% extends "BASE.html" %}
{% load static %}
{% block 'titre_page' %} Gestion des employés - Mon profil {% endblock %}
{% block 'contenu' %}
<div class="card shadow-sm border-0 w-100" style="max-width: 850px;">
<div class="card-header bg-orange-dark">
<h5 class="mb-0 fw-bold">
<i class="bi bi-pencil-square me-2"></i> Modifier mon profil
</h5>
</div>
<div class="card-body">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="accordion" id="accordionProfil">
<div class="accordion-item">
<h2 class="accordion-header" id="headingInfo">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseInfo" aria-expanded="false" aria-controls="collapseInfo">
<i class="bi bi-person-lines-fill me-2"></i> Informations personnelles
</button>
</h2>
<div id="collapseInfo" class="accordion-collapse collapse" aria-labelledby="headingInfo" data-bs-parent="#accordionProfil">
<div class="accordion-body">
<div class="row">
<div class="col">
<div class="form-group mb-2">
<label>Photo de profil :</label>
<input type="file" class="form-control">
</div>
<div class="form-group mb-2">
<label>Prénom :</label>
<input type="text" class="form-control">
</div>
<div class="form-group mb-2">
<label>Nom :</label>
<input type="text" class="form-control">
</div>
</div>
<div class="col">
<div class="form-group mb-2">
<label>Email :</label>
<input type="email" class="form-control">
</div>
<div class="form-group mb-2">
<label>Téléphone :</label>
<input type="text" class="form-control">
</div>
<div class="form-group mb-2">
<label>Adresse :</label>
<input type="text" class="form-control">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingDocs">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseDocs" aria-expanded="false" aria-controls="collapseDocs">
<i class="bi bi-file-earmark-text me-2"></i> Documents
</button>
</h2>
<div id="collapseDocs" class="accordion-collapse collapse" aria-labelledby="headingDocs" data-bs-parent="#accordionProfil">
<div class="accordion-body">
<div class="row">
<div class="col">
<div class="form-group mb-2">
<label>CV :</label>
<input type="file" class="form-control">
</div>
<div class="form-group mb-2">
<label>Diplome :</label>
<input type="file" class="form-control">
</div>
</div>
<div class="col">
<div class="form-group mb-2">
<label>RIB :</label>
<input type="file" class="form-control">
</div>
<div class="form-group mb-2">
<label>Casier judiciaire :</label>
<input type="file" class="form-control">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="d-flex justify-content-end mt-4 gap-2 flex-wrap">
<a href="{% url 'gestion_employe:mon-profil' %}" class="btn btn-outline-secondary">
<i class="bi bi-x-circle me-1"></i> Annuler
</a>
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-circle me-1"></i> Enregistrer
</button>
</div>
</form>
</div>
</div>
{% endblock %}
{% block 'modal' %}
{% endblock %}
{% block 'js' %}
{% endblock %}

View File

@@ -0,0 +1,28 @@
<div class="modal fade" id="modalAffectation" tabindex="-1" aria-labelledby="modalAffectationLabel" aria-hidden="true">
<div class="modal-dialog ">
<div class="modal-content">
<div class="modal-header bg-orange-dark">
<h5 class="modal-title" id="modalAffectationLabel" >
Affectation de projet (<span id="affectation-nom-employe"></span>)
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal">
</button>
</div>
<div class="modal-body">
<form action="{% url 'gestion_employe:affecter_employe_projet' %}" method="POST">
{% csrf_token %}
<input type="hidden" name="affecter_employe_id" id="affecter_employe_id">
{{ affectation_form.as_p }}
<div class="modal-footer bg-light">
<button type="submit" class="btn btn-success">
<i class="bi bi-check-circle"></i> affecter
</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="bi bi-x-circle me-1"></i> Annuler
</button>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,21 @@
<div class="modal fade" id="modalAjouterFormation" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-orange-dark">
<h5 class="modal-title">Ajouter un certificat</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form method="post" action="{% url 'gestion_employe:ajouter_formation' %}" enctype="multipart/form-data">
{% csrf_token %}
{{ formation_form.as_p }}
<div class="modal-footer">
<button type="submit" class="btn btn-success">Enregistrer</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,57 @@
<div class="modal fade" id="modalContrat" tabindex="-1" aria-labelledby="modalContratLabel" aria-hidden="true">
<div class="modal-dialog ">
<div class="modal-content bg-white">
<div class="modal-header ">
<h5 class="modal-title" id="modalContratLabel">
<i class="bi bi-file-earmark-text me-2"></i>
Contrat de <span id="contrat-employe"></span>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form action="{% url 'gestion_employe:creation-contrat' %}" method="POST" id="contrat_form" enctype="multipart/form-data">
{% csrf_token %}
<input type="hidden" name="employe_id" value="" id="employeIdInput">
{{ contrat_form.as_p }}
<div class="modal-footer bg-light">
<button type="submit" class="btn btn-success">
<i class="bi bi-check-circle me-1"></i> Enregistrer
</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="bi bi-x-circle me-1"></i> Annuler
</button>
</div>
</form>
{% comment %} <fieldset class="border p-3 mb-4">
<legend class="fw-bold mb-3 text-muted">Informations du contrat</legend>
<div class="form-group mb-2">
<label>Numero de contrat :</label>
<input type="text" class="form-control">
</div>
<div class="form-group mb-2">
<label>Type contrat :</label>
<input type="text" class="form-control">
</div>
<div class="form-group mb-2">
<label>date de début :</label>
<input type="date" class="form-control">
</div>
<div class="form-group mb-2">
<label>date de fin :</label>
<input type="date" class="form-control">
</div>
<div class="form-group mb-2">
<label>Salaire mensuel :</label>
<input type="text" class="form-control">
</div>
<div class="form-group mb-2">
<label>Fichier du contrat :</label>
<input type="file" class="form-control">
</div>
</div>
</fieldset> {% endcomment %}
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,136 @@
{% load tags_personnaliser %}
<div class="modal fade" id="modalDetailEmploye" tabindex="-1" aria-labelledby="modalDetailEmployeLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header ">
<h5 class="modal-title bi-pencil-square" id="modalDetailEmployeLabel">
Détails de l'employé <span id="detail-employe"></span>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="modalDetailEmployeBody" data-url="{% url 'gestion_employe:enregistrer-detail-employe' %}">
<div class="row g-3">
<input type="hidden" id="detail-id">
<div class="col-md-6">
{% csrf_token %}
<div class="form-group mb-2">
<label>Matricule :</label>
<input type="text" class="form-control" id="detail-matricule">
</div>
<div class="form-group mb-2">
<label>Date d'embauche :</label>
<input type="date" class="form-control" id="detail-dateEmbauche">
</div>
<div class="form-group mb-2">
<label>Département :</label>
<input type="text" class="form-control" id="detail-departement">
</div>
<div class="form-group mb-2">
<label>Sexe :</label>
<select id="detail-sexe" class="form-select">
<option value="">-- Sélectionner --</option>
<option value="h">Masculin</option>
<option value="f">Féminin</option>
</select>
</div>
<div class="form-group mb-2">
<label>Date de naissance :</label>
<input type="date" class="form-control" id="detail-dateNaissance">
</div>
</div>
<div class="col-md-6">
<div class="form-group mb-2">
<label>Fonction :</label>
<select class="form-select" id='detail-fonction'>
<option value='chauffeur'>Chauffeur</option>
<option value='technicien_surface'>Technicien de surface</option>
<option value='qualiticien'>Qualiticien</option>
<option value='post_doctorant'>Post-Doctorant</option>
<option value='logisticien'>Logisticien</option>
<option value='data_manager'>Data Manager</option>
<option value='raf'>RAF</option>
<option value='comptable'>Comptable</option>
<option value='assistant_direction'>Assistante de direction</option>
<option value='directeur'>Directeur</option>
</select>
</div>
<div class="form-group mb-2">
<label>Email :</label>
<input type="email" class="form-control" id="detail-email">
</div>
<div class="form-group mb-2">
<label>Téléphone :</label>
<input type="text" class="form-control" id="detail-telephone">
</div>
<div class="form-group mb-2">
<label>Adresse :</label>
<input type="text" class="form-control" id="detail-adresse">
</div>
</div>
</div>
<div class="row">
{% if user|has_group:"ressource_humaine" %}
<div class="col">
<button type="button" class="btn btn-success d-block m-auto" id="enregistrerDetail">
<i class="bi bi-save"></i> Enregistrer
</button>
</div>
{% endif %}
</div>
<div class="row">
<div class="col text-end">
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalDocuments">
<i class="bi bi-folder2-open"></i> Voir les documents RH
</button>
</div>
</div>
<hr class="my-4">
<div class="row">
<div class="col">
<div class="d-flex justify-content-between">
<h5>
<i class="bi bi-file-earmark-text me-2"></i> Liste des Contrats
</h5>
{% if user|has_group:"ressource_humaine" %}
<div>
<button class="btn btn-secondary" disabled>
<i class="bi bi-file-earmark-lock"></i> Contrat actif
</button>
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#modalContrat{{ item.employe.id }}">
<i class="bi bi-file-earmark-text"></i> Créer contrat
</button>
</div>
{% endif %}
</div>
{% csrf_token %}
<div class="row" id="contrats-list"></div>
</div>
</div>
<hr class="my-4">
<div class="row d-flex g-3">
<div class="d-flex justify-content-between">
<h5><i class="bi bi-pin-angle me-2"></i> Affectations</h5>
{% if user|has_group:"ressource_humaine" %}
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#modalAffectation" onclick="setEmployeId()">
<i class="bi bi-file-earmark-text"></i>Affecter
</button>
{% endif %}
</div>
<div class="row">
<div id="affectations-list"></div>
</div>
</div>
<hr class="my-4">
<div class="row g-3">
<h5 class="mb-3"><i class="bi bi-award-fill me-2"></i> Formations</h5>
<div class="row" id="formations-list"></div>
</div>
</div>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="bi bi-x-circle"></i> Fermer
</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,41 @@
<div class="modal fade" id="modalDocuments" tabindex="-1" aria-labelledby="modalDocumentsLabel" aria-hidden="true">
<div class="modal-dialog ">
<div class="modal-content border-0 shadow-lg">
<div class="modal-header ">
<h5 class="modal-title" id="modalDocumentsLabel">
<i class="bi bi-folder2-open me-2"></i> Documents RH de <span id="document-employe"></span>
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row g-3">
<div class="col">
<ul class="list-group">
<li class="list-group-item">
<strong>Diplôme :</strong>
<a id="document-diplome" target="_blank"></a>
</li>
<li class="list-group-item">
<strong>CV :</strong>
<a id="document-cv" target="_blank"></a>
</li>
<li class="list-group-item">
<strong>RIB :</strong>
<a id="document-rib" target="_blank"></a>
</li>
<li class="list-group-item">
<strong>Casier judiciaire :</strong>
<a id="document-casier-judiciaire" target="_blank"></a>
</li>
</ul>
</div>
</div>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="bi bi-x-circle"></i> Fermer
</button>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,17 @@
<div class="modal fade" id="modalListeDesContratExpirants" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Contrats expirants dans un maximum de 60 jours</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id='listeContratExpirant'></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,36 @@
<div class="modal fade" id="modalModifierProfil" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="bi bi-person-lines-fill me-2"></i> Modifier le mot de passe</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form method="POST" action="{% url 'gestion_employe:modifier-mot-passe' %}">
{% csrf_token %}
<div class="form-group mb-2">
<label>Votre mot de passe actuel :</label>
<input class="form-control" type="password" name="ancien-mdp">
</div>
<div class="form-group mb-2">
<label>Votre nouveau mot de passe :</label>
<input class="form-control" type="password" name="nouveau-mdp">
</div>
<div class="form-group mb-2">
<label>Confirmez votre nouveau mot de passe :</label>
<input class="form-control" type="password" name="confirmation-mdp">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
Fermer
</button>
<button type="submit" class="btn btn-success">
<i class="bi bi-check-circle me-1"></i> Enregistrer
</button>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,24 @@
"""Tags personnalisés pour les templates d'authentification.
Ce fichier contient des tags personnalisés pour les templates du projet lié à
la gestion de l'accès des utilisateurs aux différentes pages.
"""
from django import template
from django.utils import timezone
from gestion_employe.models import Affectation
register = template.Library()
@register.filter
def has_group(user, group_name):
"""Vérifiez si un utilisateur appartient à un groupe spécifique."""
return user.groups.filter(name=group_name).exists()
@register.filter
def is_chef_projet(user):
try:
affectation = Affectation.objects.get(employe__user__username = user, date_fin_daffectation__gte = timezone.now().date())
except Affectation.DoesNotExist:
return False
return affectation.role == "chef_projet"

3
gestion_employe/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -261,6 +261,7 @@ def creation_contrat(request):
except Employe.DoesNotExist: except Employe.DoesNotExist:
messages.error(request, "Employé non trouvé.") messages.error(request, "Employé non trouvé.")
return redirect('employe-index') return redirect('employe-index')
contrat_actif = Contrat.objects.filter( contrat_actif = Contrat.objects.filter(
employe=employe, employe=employe,
date_fin__gte=date.today() date_fin__gte=date.today()

View File

6
gestion_projet/admin.py Normal file
View File

@@ -0,0 +1,6 @@
from django.contrib import admin
from .models import DomaineDeRecherche
@admin.register(DomaineDeRecherche)
class DomaineDeRecherche(admin.ModelAdmin):
list_display = ('nom',)

176
gestion_projet/forms.py Normal file
View File

@@ -0,0 +1,176 @@
from django import forms
from gestion_projet.models import Projet
from .models import (
ActiviteProjet,
Bailleur,
DocumentProjet,
FinancementProjet,
LivrablesLivres,
DomaineDeRecherche
)
class ProjetForm(forms.ModelForm):
"""Formulaire de création et de modification d'un projet, avec validation des dates et personnalisation des champs."""
class Meta:
model = Projet
fields = (
'id_projet',
'nom_projet',
'date_debut',
'date_fin',
'numero_convention',
'domaine_recherche',
'type_projet',
'budget',
'budget_RH',
'description'
)
# domaine_recherche = forms.ModelMultipleChoiceField(
# queryset=DomaineDeRecherche.objects.all(),
# to_field_name="nom",
# required=False
# )
widgets = {
'id_projet': forms.TextInput(attrs={'class': "form-control"}),
'nom_projet': forms.TextInput(attrs={'class': "form-control"}),
'numero_convention': forms.TextInput(attrs={'class': "form-control"}),
'domaine_recherche': forms.SelectMultiple(attrs={'class': "form-control"}),
'type_projet': forms.Select(attrs={'class': "form-select"}),
'budget': forms.NumberInput(attrs={'class': "form-control"}),
'budget_RH': forms.NumberInput(attrs={'class': "form-control"}),
'description': forms.Textarea(attrs={'class': "form-control"}),
'date_debut': forms.DateInput(attrs={'type': 'date', 'class': "form-control"}),
'date_fin': forms.DateInput(attrs={'type': 'date', 'class': "form-control"}),
}
class BailleurForm(forms.ModelForm):
"""
Formulaire de création et de modification d'un bailleur,
avec validation des champs et personnalisation des labels.
"""
class Meta:
model = Bailleur
fields = ('nom_organisme', 'contact', 'email', 'pays')
widgets = {
'nom_organisme':forms.TextInput(attrs={
'class':'form-control',
}),
'contact':forms.TextInput(attrs={
'class':'form-control',
}),
'email':forms.TextInput(attrs={
'class':'form-control',
}),
'pays':forms.TextInput(attrs={
'class':'form-control',
}),
}
class DocumentProjetForm(forms.ModelForm):
"""
Formulaire pour ajouter ou modifier un document associé à un projet,
avec validation des champs et personnalisation des labels.
"""
class Meta:
model = DocumentProjet
fields = [
'nom_document',
'numero',
'date_validite',
'fichier',
'description'
]
widgets = {
'nom_document': forms.Select(attrs={'class': 'form-select'}),
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'numero': forms.TextInput(attrs={'class': 'form-control'}),
'date_validite': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'fichier': forms.ClearableFileInput(attrs={'class': 'form-control'}),
}
class ActiviteProjetForm(forms.ModelForm):
"""Formulaire pour créer ou modifier une activité de projet, avec validation des champs et personnalisation des widgets."""
class Meta:
model = ActiviteProjet
fields = (
'titre',
'date_debut',
'date_fin',
'besoin_ressource_materielle',
'budget_prevu',
'description',
)
widgets = {
'titre':forms.TextInput(attrs={
'class':'form-control',
'placeholder':'Titre de lactivité'
}),
'description':forms.Textarea(attrs={
'class':'form-control',
'rows':3,
'placeholder':'Description de lactivité'
}),
'date_debut':forms.DateInput(attrs={
'class':'form-control',
'type':'date'
}),
'date_fin':forms.DateInput(attrs={
'class':'form-control',
'type':'date'
}),
'besoin_ressource_materielle': forms.Textarea(attrs={
'class':'form-control',
'rows':3,
'placeholder':'Besoin de ressources matérielles'
}),
'budget_prevu': forms.NumberInput(attrs={
'class':'form-control',
'placeholder':'Budget prévu'
}),
}
class FinancementProjetFrom(forms.ModelForm):
"""Formulaire pour créer ou modifier le financement relatif à un projet."""
class Meta:
model = FinancementProjet
fields = (
'projet',
'bailleur',
'pourcentage',
)
widgets = {
'projet':forms.Select(attrs={
'class':'form-select',
}),
'bailleur':forms.Select(attrs={
'class':'form-select',
}),
'pourcentage':forms.NumberInput(attrs={
'class':'form-control',
}),
}
class LivrablesLivresForm(forms.ModelForm):
"""Formulaire pour créer ou modifier un livrable livré dans le cadre d'une activité de projet."""
class Meta:
model = LivrablesLivres
fields = (
'activite',
'nom',
'fichier',
)
widgets = {
'activite': forms.Select(attrs={
'class':'form-select',
}),
'nom': forms.TextInput(attrs={
'class':'form-control',
'placeholder':'Nom du livrable'
}),
'fichier': forms.ClearableFileInput(attrs={
'class':'form-control',
}),
}

View File

@@ -0,0 +1,107 @@
# Generated by Django 5.2.13 on 2026-04-17 12:03
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='ActiviteProjet',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('titre', models.CharField(max_length=200, verbose_name="Titre de l'activité")),
('description', models.TextField(blank=True, null=True, verbose_name="Description de l'activité")),
('date_debut', models.DateField(verbose_name='Date de début')),
('date_fin', models.DateField(verbose_name='Date de fin')),
('annuler', models.BooleanField(default=False, verbose_name="Annuler l'activité")),
('motif_annulation', models.TextField(blank=True, null=True, verbose_name="Motif d'annulation")),
('motif_changement_budget', models.TextField(blank=True, null=True, verbose_name='Motif de changement de budget')),
('budget_prevu', models.DecimalField(decimal_places=2, default=0, max_digits=15, verbose_name='Budget prévu')),
('budget_depense', models.DecimalField(decimal_places=2, default=0, max_digits=15, verbose_name='Budget dépensé')),
('besoin_ressource_materielle', models.TextField(verbose_name='Besoin de ressources matérielles')),
],
),
migrations.CreateModel(
name='Bailleur',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nom_organisme', models.CharField(max_length=200, unique=True)),
('contact', models.CharField(blank=True, max_length=100, null=True)),
('email', models.EmailField(blank=True, max_length=254, null=True)),
('pays', models.CharField(blank=True, max_length=100, null=True)),
],
),
migrations.CreateModel(
name='DomaineDeRecherche',
fields=[
('nom', models.CharField(choices=[('sciences_sociales', 'Sciences sociales'), ('naturelles', 'Naturelles'), ('humaines', 'Humaines'), ('veterinaires', 'Vétérinaires')], max_length=100, primary_key=True, serialize=False, verbose_name='Domaine de recherche')),
],
options={
'verbose_name': 'Domaine de recherche',
'verbose_name_plural': 'Domaines de recherche',
},
),
migrations.CreateModel(
name='LivrablesLivres',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nom', models.CharField(max_length=255, verbose_name='Nom du livrable')),
('fichier', models.FileField(blank=True, null=True, upload_to='fichier_livrables/')),
('activite', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_projet.activiteprojet')),
],
),
migrations.CreateModel(
name='Projet',
fields=[
('id_projet', models.CharField(blank=True, max_length=100, primary_key=True, serialize=False, unique=True, verbose_name='ID du projet')),
('nom_projet', models.CharField(max_length=200, verbose_name='Nom du projet')),
('date_debut', models.DateField(verbose_name='Date de début')),
('date_fin', models.DateField(verbose_name='Date de fin')),
('numero_convention', models.CharField(max_length=100, verbose_name='Numéro de convention')),
('description', models.TextField(verbose_name='Description')),
('type_projet', models.CharField(choices=[('laboratoire', 'Laboratoire'), ('épidémiologie', 'Épidémiologie'), ('sciences sociales', 'Sciences sociales'), ('cliniques', 'Cliniques'), ('autre', 'Autre')], default='épidémiologie', max_length=100, verbose_name='Type de projet')),
('budget', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Budget')),
('budget_RH', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Budget RH')),
('created_at', models.DateTimeField(auto_now_add=True)),
('bailleur', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='gestion_projet.bailleur', verbose_name='Bailleur de fonds')),
('domaine_recherche', models.ManyToManyField(to='gestion_projet.domainederecherche')),
],
),
migrations.CreateModel(
name='DocumentProjet',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date_ajout', models.DateTimeField(auto_now_add=True, verbose_name="Date d'ajout")),
('nom_document', models.CharField(choices=[('protocole', 'Protocole détude'), ('ethique', "Approbation du comité d'éthique"), ('autorisation', 'Autorisation (DNLP)'), ('rapport_technique', 'Rapport technique'), ('rapport_financier', 'Rapport financier'), ('rapport_avancement', "Rapport d'avancement"), ('convention', 'Convention'), ('rapport_final', 'Rapport final'), ('autre', 'Autre')], max_length=100, verbose_name='Type de document')),
('description', models.TextField(blank=True, verbose_name='Description')),
('numero', models.CharField(blank=True, max_length=100, null=True, verbose_name='Numéro du document')),
('date_validite', models.DateField(blank=True, null=True, verbose_name='Date de validité')),
('fichier', models.FileField(upload_to='documents_projets/', verbose_name='Fichier à télécharger')),
('projet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='gestion_projet.projet', verbose_name='Projet')),
],
),
migrations.AddField(
model_name='activiteprojet',
name='projet',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_projet.projet', verbose_name='Projet'),
),
migrations.CreateModel(
name='FinancementProjet',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('pourcentage', models.DecimalField(decimal_places=2, max_digits=5)),
('bailleur', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_projet.bailleur')),
('projet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_projet.projet')),
],
options={
'unique_together': {('projet', 'bailleur')},
},
),
]

View File

309
gestion_projet/models.py Normal file
View File

@@ -0,0 +1,309 @@
from django.db import models
from datetime import date
from django.utils import timezone
class Bailleur(models.Model):
"""Modèle représentant un bailleur de fonds pour les projets de recherche."""
nom_organisme = models.CharField(
max_length=200,
unique=True
)
contact = models.CharField(
max_length=100,
blank=True,
null=True
)
email = models.EmailField(
blank=True,
null=True
)
pays = models.CharField(
max_length=100,
blank=True,
null=True
)
def __str__(self):
return self.nom_organisme
class DomaineDeRecherche(models.Model):
"""Modèle représentant les domaines de recherche"""
DOMAINE_RECHERCHE = [
('sciences_sociales', 'Sciences sociales'),
('naturelles', 'Naturelles'),
('humaines', 'Humaines'),
('veterinaires', 'Vétérinaires')
]
nom = models.CharField(
max_length=100,
verbose_name="Domaine de recherche",
choices=DOMAINE_RECHERCHE,
primary_key=True
)
class Meta:
verbose_name = 'Domaine de recherche'
verbose_name_plural = 'Domaines de recherche'
def __str__(self):
return self.nom
class Projet(models.Model):
"""Modèle représentant un projet de recherche avec ses caractéristiques et son bailleur associé."""
TYPE_PROJET = [
('laboratoire', 'Laboratoire'),
('épidémiologie', 'Épidémiologie'),
('sciences sociales', 'Sciences sociales'),
('cliniques', 'Cliniques'),
('autre', 'Autre'),
]
id_projet = models.CharField(
max_length=100,
blank=True,
unique=True,
primary_key=True,
verbose_name="ID du projet"
)
nom_projet = models.CharField(
max_length=200,
verbose_name="Nom du projet"
)
date_debut = models.DateField(
verbose_name="Date de début"
)
date_fin = models.DateField(
verbose_name="Date de fin"
)
numero_convention = models.CharField(
max_length=100,
verbose_name="Numéro de convention"
)
description = models.TextField(
verbose_name="Description"
)
type_projet = models.CharField(
max_length=100,
choices=TYPE_PROJET,
default='épidémiologie',
verbose_name="Type de projet"
)
domaine_recherche = models.ManyToManyField(DomaineDeRecherche)
budget=models.DecimalField(
max_digits=12,
decimal_places=2,
verbose_name="Budget"
)
budget_RH = models.DecimalField(
max_digits=12,
decimal_places=2,
verbose_name="Budget RH"
)
created_at = models.DateTimeField(auto_now_add=True)
bailleur = models.ForeignKey(
Bailleur,
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name="Bailleur de fonds"
)
@property
def statut(self):
if self.date_fin < date.today():
return "Terminé"
return "En cours"
@property
def avancement(self):
aujourd_hui = date.today()
if (self.date_debut and self.date_fin) and (self.date_debut < self.date_fin):
duree_projet = (self.date_fin - self.date_debut).days
temps_ecoule = (aujourd_hui - self.date_debut).days
if duree_projet > 0:
return round((temps_ecoule / duree_projet) * 100, 2)
return 0
def __str__(self):
return f"{self.nom_projet}"
class FinancementProjet(models.Model):
"""
Modèle représentant le financement d'un projet par un bailleur,
avec le pourcentage de contribution.
"""
projet = models.ForeignKey(
Projet,
on_delete=models.CASCADE
)
bailleur = models.ForeignKey(
Bailleur,
on_delete=models.CASCADE
)
pourcentage = models.DecimalField(
max_digits = 5,
decimal_places=2
)
class Meta:
unique_together = ('projet', 'bailleur')
def __str__(self):
return f"{self.bailleur.nom} - {self.projet.nom_projet} ({self.pourcentage}%)"
class DocumentProjet(models.Model):
"""Modèle représentant un document associé à un projet, avec des métadonnées et un fichier attaché."""
NOM_DOCUMENT_CHOICES = [
('protocole', 'Protocole détude'),
('ethique', "Approbation du comité d'éthique"),
('autorisation', 'Autorisation (DNLP)'),
('rapport_technique', 'Rapport technique'),
('rapport_financier', 'Rapport financier'),
('rapport_avancement', "Rapport d'avancement"),
('convention', 'Convention'),
('rapport_final', 'Rapport final'),
('autre', 'Autre'),
]
projet = models.ForeignKey(
Projet,
on_delete=models.CASCADE,
related_name='documents',
verbose_name="Projet"
)
date_ajout = models.DateTimeField(
auto_now_add=True,
verbose_name="Date d'ajout"
)
nom_document = models.CharField(
max_length = 100,
choices = NOM_DOCUMENT_CHOICES,
verbose_name="Type de document"
)
description = models.TextField(
blank = True,
verbose_name = "Description"
)
numero = models.CharField(
max_length = 100,
blank = True,
null = True,
verbose_name = "Numéro du document"
)
date_validite = models.DateField(
blank = True,
null = True,
verbose_name = "Date de validité"
)
fichier = models.FileField(
upload_to = 'documents_projets/',
verbose_name = "Fichier à télécharger"
)
def __str__(self):
return f"{self.nom_document} ({self.projet})"
class ActiviteProjet(models.Model):
"""Modèle représentant le planning d'un projet, avec des activités associées et un statut."""
projet = models.ForeignKey(
Projet,
on_delete = models.CASCADE,
verbose_name = "Projet"
)
titre = models.CharField(
max_length = 200,
verbose_name = "Titre de l'activité"
)
description = models.TextField(
blank = True,
null = True,
verbose_name = "Description de l'activité"
)
date_debut = models.DateField(verbose_name="Date de début")
date_fin = models.DateField(verbose_name="Date de fin")
annuler = models.BooleanField(
default = False,
verbose_name = "Annuler l'activité"
)
motif_annulation = models.TextField(
blank = True,
null = True,
verbose_name = "Motif d'annulation"
)
motif_changement_budget = models.TextField(
blank = True,
null = True,
verbose_name = "Motif de changement de budget"
)
budget_prevu = models.DecimalField(
max_digits = 15,
decimal_places = 2,
default = 0,
verbose_name = "Budget prévu"
)
budget_depense = models.DecimalField(
max_digits = 15,
decimal_places = 2,
default = 0,
verbose_name = "Budget dépensé"
)
besoin_ressource_materielle = models.TextField(
verbose_name="Besoin de ressources matérielles"
)
@property
def statut(self):
today = timezone.now().date()
if not self.annuler:
if self.date_fin < today:
return 'Terminé'
elif self.date_debut > today:
return 'À venir'
else:
return 'En cours'
else:
return 'Annulé'
def __str__(self):
return f"{self.titre} ({self.projet.nom_projet})"
# class LivrableAttendu(models.Model):
# """
# Modèle représentant un livrable attendu pour une activité de projet,
# avec des critères de validation.
# """
# activite = models.ForeignKey(
# ActiviteProjet,
# on_delete = models.CASCADE,
# related_name = "livrables_attendus"
# )
# nom = models.CharField(max_length=255)
# def __str__(self):
# return f"{self.nom} (Activité: {self.activite.titre})"
class LivrablesLivres(models.Model):
"""Modèle représentant un livrable livré pour une activité de projet."""
activite = models.ForeignKey(
ActiviteProjet,
on_delete = models.CASCADE
)
# nom = models.ForeignKey(
# LivrableAttendu,
# on_delete = models.CASCADE
# )
nom = models.CharField(
max_length=255,
verbose_name="Nom du livrable"
)
fichier = models.FileField(
upload_to = 'fichier_livrables/',
blank = True,
null = True
)
def __str__(self):
return self.nom

View File

@@ -0,0 +1,19 @@
const boutonEnregistrerProjet = $("btnEnregistrerProjet");
boutonEnregistrerProjet.addEventListener("click", function() {
const formulaire = $("formCreationProjet");
const formData = new FormData(formulaire);
fetch(formulaire.action, {
method: "POST",
body: formData,
headers: {
"X-CSRFToken": formData.get("csrfmiddlewaretoken")
}
})
.then(response => {
if (response.ok) {
window.location.reload();
alert("Projet enregistré avec succès !");
}
});
});

View File

@@ -23,9 +23,8 @@ btnEnregistrerBailleur.addEventListener('click', function() {
}); });
}); });
document.addEventListener("DOMContentLoaded", function () {
table = new Tabulator("#table-bailleurs", { table = new Tabulator("#table-bailleurs", {
ajaxURL: "/gestion-projet/bailleurs/", ajaxURL: "/gestion-projet/bailleurs/",
layout: "fitColumns", layout: "fitColumns",
pagination: "local", pagination: "local",
@@ -45,7 +44,4 @@ document.addEventListener("DOMContentLoaded", function () {
supprimerBailleur(data.id); supprimerBailleur(data.id);
} }
} }
});
}); });

View File

@@ -0,0 +1,22 @@
const btn_enregistrer_financement = document.getElementById('btn_enregistrer_financement');
btn_enregistrer_financement.addEventListener('click', function() {
const form = document.getElementById('form_financement');
const formData = new FormData(form);
fetch(form.action, {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': formData.get('csrfmiddlewaretoken')
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert(data.message);
window.location.reload();
}else {
alert(data.message);
}
});
});

View File

@@ -0,0 +1,64 @@
const $ = (element) => document.getElementById(element)
const url_liste_projet = $("tableau-liste-projet").dataset.url;
const tableau_liste_projet = new Tabulator("#tableau-liste-projet", {
fitColumns: true,
responsiveLayout : true,
columns: [
{title: "Projet", field: "nom_projet"},
{title: "Source de financement", field: "source_financement"},
{title: "Budget Total", field: "budget"},
{title: "Budget RH", field: "budget_RH"},
{title: "Avancement", field: "avancement", formatter: "progress"},
{title: "Statut", field: "statut"},
],
ajaxURL: url_liste_projet
})
const employes_affectes_projet = new Tabulator("#employes_affectes_projet", {
columns: [
{title: "Employé", field: "employe"},
{title: "Pourcentage d'affectation", field: "pourcentage_affectation"},
],
placeholder: "Aucun employé affecté pour ce projet",
})
const bailleurs_projet = new Tabulator("#bailleurs_projet", {
columns: [
{title: "Bailleur", field: "bailleur"},
{title: "Pourcentage de financement", field: "pourcentage_financement"},
],
placeholder: "Aucun bailleur attribué pour ce projet",
})
tableau_liste_projet.on("rowClick", function (row, rowData) {
const data = rowData.getData();
const modal = new bootstrap.Modal($("modalDetailProjet"));
$("detail_id_projet").value = data.id_projet;
$("detail_nom_projet").value = data.nom_projet;
$("detail_date_debut").value = data.date_debut;
$("detail_date_fin").value = data.date_fin;
$("detail_numero_convention").value = data.numero_convention;
$("detail_type_projet").value = data.type_projet;
Array.from($("detail_domaine_recherche").options).forEach(option => {
if (data.domaine_recherche.includes(option.value)) {
option.selected = true;
} else {
option.selected = false;
}
});
$("detail_budget").value = data.budget;
$("detail_budget_rh").value = data.budget_RH;
$("detail_description").value = data.description;
$("detail_statut").value = data.statut;
employes_affectes_projet.setData(`projet/liste-employes-par-projet/${$("detail_id_projet").value}`);
bailleurs_projet.setData(`projet/bailleurs/${data.id_projet}/`);
modal.show();
})
// $('detail-projet-form').addEventListener('submit', (e) => {
// e.preventDefault();
// new FormData($("detail-projet-form"));
// })

View File

@@ -0,0 +1,20 @@
const urlListeDocument = document.getElementById('listeDocuments').dataset.urllistedocument;
const table_liste_documents = new Tabulator(document.getElementById('listeDocuments'), {
layout: "fitColumns",
placeholder: "Aucun document trouvé",
columns: [
{ title: "Nom du Document", field: "nom_document" },
{ title: "Numéro", field: "numero" },
{ title: "Date de Validité", field: "date_validite", formatter: "datetime", formatterParams: {
inputFormat: "yyyy-MM-dd",
outputFormat: "dd/MM/yyyy"
}
},
{ title: "Lien vers le Document", field: "lien_document", formatter:"link", formatterParams:{
target:"_blank",
}
},
],
ajaxURL: urlListeDocument,
});

View File

@@ -0,0 +1,59 @@
const $ = (element) => document.getElementById(element)
const url_liste_activite = $("tableau-liste-activite").dataset.urllisteactivite
const tableau_liste_activite = new Tabulator("#tableau-liste-activite", {
columns: [
{title: "Activité", field: "titre"},
{title: "Date début", field: "date_debut"},
{title: "Date fin", field: "date_fin"},
{title: "Budget prévu", field: "budget_prevu"},
{title: "Budget dépensé", field: "budget_depense"},
{title: "Motif de changement de budget", field: "motif_changement_budget"},
{title: "Statut", field: "statut"},
],
ajaxURL: url_liste_activite,
})
tableau_liste_activite.on("rowClick", function (row, rowData) {
const data = rowData.getData();
$("idDetailActivite").value = data.id;
$("titreDetailActivite").value = data.titre;
$("descriptionDetailActivite").value = data.description;
$("date_debutDetailActivite").value = data.date_debut;
$("date_finDetailActivite").value = data.date_fin;
$("statutDetailActivite").value = data.statut;
$("budget_prevuDetailActivite").value = data.budget_prevu;
$("besoin_ressources_materiellesDetailActivite").value = data.besoin_ressource_materielle;
const modal = new bootstrap.Modal($("modalDetailActivite"));
modal.show();
fetch(`liste-des-livrables/${data.id}/`)
.then(response => response.json())
.then(livrables => {
tableau_liste_livrable.setData(livrables);
})
})
const tableau_liste_livrable = new Tabulator("#listeLivrables", {
columns: [
{title: "Livrable", field: "titre"},
{title: "Lien du livrable", field: "lien", formatter: "link", formatterParams: {blank: true}},
],
placeholder: "Aucun livrable trouvé",
})
$("btnMiseAJourDepense").addEventListener("click", function() {
const modal = new bootstrap.Modal($("modalDepenseActivite"));
bootstrap.Modal.getOrCreateInstance($("modalDetailActivite")).hide();
const idActivite = $("idDetailActivite").value;
const budgetPrevu = $("budget_prevuDetailActivite").value;
$("id_activite_depense").value = idActivite;
$("budget_prevu").value = budgetPrevu;
modal.show();
})
$("btnAnnulerActivite").addEventListener("click", function(event) {
new bootstrap.Modal($("modalAnnulerActivite")).show();
$("id_activite_annulation").value = $("idDetailActivite").value;
bootstrap.Modal.getOrCreateInstance($("modalDetailActivite")).hide();
})

View File

@@ -0,0 +1,37 @@
{% extends "BASE.html" %}
{% load static %}
{% block 'titre_page' %} Gestion des projets {% endblock %}
{% block 'contenu' %}
<button class="btn btn-primary mb-3">
<i class="bi bi-caret-left-fill"></i> Retour
</button>
{% if messages %}
{% for message in messages %}
<div class="alert alert-{% if message.tags == "error" %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
<div class="row d-flex justify-content-center">
<div class="col-8 card bordered rounded py-4 px-2 ">
<h5>Ajout du financement au projet (Nom du projet ici)</h5>
<hr>
<form method="POST" action="{% url 'gestion_projet:creation-projet' %}">
{% csrf_token %}
{{ formulaire_creation_projet.as_p }}
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-danger">Annuler</button>
<a href="{% url 'gestion_projet:ajouter_financement' %}" class="btn btn-primary ms-2">Enregistrer et ajouter un financement</a>
</div>
</form>
</div>
</div>
{% endblock %}
{% block 'modal' %}
{% include "gestion_projet/parts/modalAjoutProjet.html" %}
{% endblock %}
{% block 'js' %}
<script type="text/javascript" src="{% static 'gestion_projet/js/index.js' %}"></script>
{% endblock %}

View File

@@ -0,0 +1,32 @@
{% extends "BASE.html" %}
{% load static %}
{% block 'titre_page' %} Gestion des projets {% endblock %}
{% block 'contenu' %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
{% comment %} <h3>Enregistrement d'un nouveau projet</h3> {% endcomment %}
<div class="row d-flex justify-content-center">
<div class="col-8 card bordered rounded py-4 px-2 ">
<h5>Enregistrement d'un nouveau projet</h5>
<hr>
<form method="POST" action="{% url 'gestion_projet:creation-projet' %}">
{% csrf_token %}
{{ formulaire_creation_projet.as_p }}
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-danger">Annuler</button>
<button class="btn btn-primary ms-2">Enregistrer</button>
</div>
</form>
</div>
</div>
{% endblock %}
{% block 'modal' %}
{% endblock %}
{% block 'js' %}
{% endblock %}

View File

@@ -0,0 +1,61 @@
{% extends "BASE.html" %}
{% load static %}
{% load tags_personnaliser %}
{% block 'titre_page' %} Gestion des projets {% endblock %}
{% block 'contenu' %}
<h3>Gestion des projets</h3>
{% if messages %}
{% for message in messages %}
<div class="alert alert-{% if message.tags == "error" %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
<div class="row">
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4">
<span class="fs-5">Projets en cours</span>
<h3>{{projet_en_cours}}</h3>
</div>
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4 mx-2">
<span class="fs-5">Budget Total (GNF)</span>
<h3>{{budget_total}}</h3>
</div>
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4">
<span class="fs-5">Personnel sous projet</span>
<h3>{{nombre_personnel}}</h3>
</div>
</div>
<div class="row mt-4">
<div class="col d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-4" >La liste des projets </h5>
{% if user|has_group:"ressource_humaine" %}
<div>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalProjet">
<i class="bi bi-plus-circle"></i> Ajouter un projet
</button>
<button class="btn btn-info" data-bs-toggle="modal" data-bs-target="#modalBailleur">
<i class="bi bi-person"></i> Ajouter un bailleur
</button>
</div>
{% endif %}
</div>
<div class="row">
<div class="col">
<div id="tableau-liste-projet" data-url="{% url 'gestion_projet:liste-projet' %}"></div>
</div>
</div>
</div>
{% endblock %}
{% block 'modal' %}
{% include "gestion_projet/parts/modalAjoutProjet.html" %}
{% include "gestion_projet/parts/modalFinancement.html" %}
{% include "gestion_projet/parts/creation_bailleur.html" %}
{% include "gestion_projet/parts/modalDetailProjet.html" %}
{% endblock %}
{% block 'js' %}
<script type="text/javascript" src="{% static 'gestion_projet/js/index.js' %}"></script>
<script type="text/javascript" src="{% static 'gestion_projet/js/creation_projet.js' %}"></script>
<script type="text/javascript" src="{% static 'gestion_projet/js/enregistrement_bailleur.js' %}"></script>
<script type="text/javascript" src="{% static 'gestion_projet/js/enregistrement_financement.js' %}"></script>
{% endblock %}

View File

@@ -48,4 +48,3 @@
</div> </div>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,16 @@
<div class="modal fade" id="modalListeDocument" tabindex="-1" aria-labelledby="modalListeDocumentLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title fw-bold" id="modalListeDocumentLabel">Liste des Documents</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<div id="listeDocuments" data-urllistedocument="{% url 'gestion_projet:liste-documents-projet' %}"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
<div class="modal fade" id="modalAjoutActivite" tabindex="-1" aria-labelledby="modalAjoutActiviteLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalAjoutActiviteLabel">Ajouter une Activité</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form method="POST" action="{% url 'gestion_projet:ajouter-activite' %}">
{% csrf_token %}
<div class="modal-body">
{{ form_ajout_activite.as_p }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-primary">Ajouter</button>
</div>
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
<div class="modal fade" id="modalAjoutDocument" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Ajouter un Document</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form method="POST" action="{% url 'gestion_projet:ajouter-document' %}" enctype="multipart/form-data">
{% csrf_token %}
<div class="modal-body">
{{ form_ajout_document.as_p }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-primary">Ajouter</button>
</div>
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
<div class="modal fade" id="modalAjouterLivrable" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Ajouter un livrable - (Nom du livrable)</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form method="post" action="{% url 'gestion_projet:ajouter-livrable' %}" enctype="multipart/form-data">
{% csrf_token %}
{{ form_ajout_livrable.as_p }}
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-success">Enregistrer</button>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,21 @@
<!-- Modale Projet -->
<div class="modal fade" id="modalProjet" tabindex="-1" aria-labelledby="modalProjetLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-orange-dark">
<h5 class="modal-title" id="modalProjetLabel">Ajouter un projet</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
</div>
<div class="modal-body p-3">
<form method="POST" id="formCreationProjet" action="{% url 'gestion_projet:creation-projet' %}">
{% csrf_token %}
{{ formulaire_creation_projet.as_p }}
</form>
</div>
<div class="modal-footer">
<button type="submit" id="btnEnregistrerProjet" class="btn btn-success">Enregistrer</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,26 @@
<div class="modal fade" id="modalAnnulerActivite" tabindex="-1" aria-labelledby="modalAnnulerActiviteLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title fw-bold" id="modalAnnulerActiviteLabel">Annuler l'activité</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<form method="POST" action="{% url 'gestion_projet:annuler-activite' %}">
{% csrf_token %}
<input type="hidden" name="id_activite" id="id_activite_annulation">
<div class="mb-3">
<label for="motif_annulation" class="form-label">Motif d'annulation</label>
<textarea class="form-control" name="motif_annulation" id="motif_annulation" rows="4" required></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-danger">
<i class="bi bi-x-circle"></i> Confirmer l'annulation
</button>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,58 @@
<div class="modal fade" id="modalDetailActivite" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-orange-dark">
<h5 class="modal-title">Détails de l'activité</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row g-3">
<div class="">
<input type="hidden" id="idDetailActivite">
<div class="form-group mb-2">
<label>Titre :</label>
<input type="text" class="form-control" id="titreDetailActivite">
</div>
<div class="form-group mb-2">
<label>Description :</label>
<textarea class="form-control" id="descriptionDetailActivite"></textarea>
</div>
<div class="form-group mb-2">
<label>Date de début :</label>
<input type="date" class="form-control" id="date_debutDetailActivite">
</div>
<div class="form-group mb-2">
<label>Date de fin :</label>
<input type="date" class="form-control" id="date_finDetailActivite">
</div>
<div class="form-group mb-2">
<label>Statut:</label>
<input type="text" class="form-control" id="statutDetailActivite">
</div>
<div class="form-group mb-2">
<label>Budget prévu :</label>
<input type="number" class="form-control" id="budget_prevuDetailActivite">
</div>
<div class="form-group mb-2">
<label>Besoin de ressources matérielles:</label>
<textarea class="form-control" id="besoin_ressources_materiellesDetailActivite"></textarea>
</div>
<p><strong>Liste des livrables :</strong></p>
<div id="listeLivrables"></div>
<button class="btn btn-danger d-block mx-auto" id="btnAnnulerActivite">
Annuler cette activité
</button>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-success" id="btnMiseAJourDepense">
<i class="bi bi-plus-circle"></i> Mise à jour du budget
</button>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalAjouterLivrable">
<i class="bi bi-plus-circle"></i> Ajouter un livrable
</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,115 @@
{% load tags_personnaliser %}
<div class="modal fade" id="modalDetailProjet{{ activite.id }}" tabindex="-1">
<div class="modal-dialog ">
<div class="modal-content">
<div class="modal-header ">
<h5 class="modal-title">Détails du projet </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row g-3">
<div class="col" id="detail-projet-container">
<form method='post' action="{% url 'gestion_projet:mises-a-jour-projet' %}" id="detail-projet-form">
{% csrf_token %}
<div class="form-group mb-2">
<label>Id projet:</label>
<input type="text" class="form-control" id="detail_id_projet" name='id_projet' readonly>
</div>
<div class="form-group mb-2">
<label> Nom projet :</label>
<input type="text" class="form-control" id="detail_nom_projet" name='nom_projet'>
</div>
<div class="form-group mb-2">
<label>Numero de convention:</label>
<input type="text" class="form-control" id="detail_numero_convention" name="numero_convention">
</div>
<div class="form-group mb-2">
<label>Date de début :</label>
<input type="date" class="form-control" id="detail_date_debut" name='date_debut'>
</div>
<div class="form-group mb-2">
<label>Date de fin :</label>
<input type="date" class="form-control" id="detail_date_fin" name='date_fin'>
</div>
<div class="form-group mb-2">
<label> Type de projet </label>
<select class="form-select" id="detail_type_projet" name='type_projet'>
<option value="laboratoire">Laboratoire</option>
<option value="épidémiologie">Épidémiologie</option>
<option value="sciences sociales">Sciences sociales</option>
<option value="cliniques">Cliniques</option>
<option value="autre">Autre</option>
</select>
</div>
<div class="form-group mb-2">
<label> Domaine de recherche </label>
<select class="form-select" multiple id="detail_domaine_recherche" name="domaine_recherche">
<option value="sciences_sociales">Sciences sociales</option>
<option value="naturelles">Naturelles</option>
<option value="humaines">Humaines</option>
<option value="veterinaires">Vétérinaires</option>
</select>
</div>
<div class="form-group mb-2">
<label>Budget :</label>
<input type="number" class="form-control" id="detail_budget" name='budget'>
</div>
<div class="form-group mb-2">
<label>Budget RH:</label>
<input type="number" class="form-control" id="detail_budget_rh" name='budget_RH'>
</div>
<div class="form-group mb-2">
<label>Description :</label>
<textarea class="form-control" id="detail_description" name='description'></textarea>
</div>
<div class="form-check-group mb-2">
<label class="form-check-label">Statut :</label>
<input type="text" class="form-control" id="detail_statut" readonly>
</div>
<button type='submit' class="btn btn-warning">
<i class="bi bi-pencil"></i> Modifier Projet
</button>
</form>
<hr class="my-4">
<div class="accordion" id="accordionDetailProjet">
<div class="accordion-item">
<h2 class="accordion-header" id="employesAffectes">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
Employés Affectés
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse show" aria-labelledby="employesAffectes" data-bs-parent="#accordionDetailProjet">
<div class="accordion-body">
<div id="employes_affectes_projet"></div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="bailleursProjet">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
Liste des bailleurs
</button>
</h2>
<div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="bailleursProjet" data-bs-parent="#accordionDetailProjet">
<div class="accordion-body">
<div id="bailleurs_projet"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
{% if user|has_group:"ressource_humaine" %}
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#modal_ajout_financement">
<i class="bi bi-plus-circle"></i> Ajouter un financement
</button>
{% endif %}
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="bi bi-x-circle"></i> Fermer
</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
<div class="modal fade" id="modal_ajout_financement" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Ajout de financement</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
</div>
<div class="modal-body">
<form method="POST" id="form_financement" action="{% url 'gestion_projet:ajouter_financement' %}">
{% csrf_token %}
{{ form_ajout_financement.as_p }}
</form>
</div>
<div class="modal-footer">
<button type="submit" id="btn_enregistrer_financement" class="btn btn-success">Enregistrer</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,36 @@
<div class="modal fade" id="modalDepenseActivite" tabindex="-1" aria-labelledby="modalDepenseActiviteLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalDepenseActiviteLabel">
<i class="bi bi-cash-stack me-2"></i> Mise à jour des dépenses
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form method="POST" action="{% url 'gestion_projet:mettre-a-jour-depense' %}">
{% csrf_token %}
<input type="hidden" name="id_activite" id="id_activite_depense">
<div class="mb-2">
<label class="form-label">Budget prévu (GNF)</label>
<input type="number" class="form-control" name="budget_prevu" id="budget_prevu" value="" disabled>
</div>
<div class="mb-2">
<label for="budget_depense" class="form-label">Montant dépensé (GNF)</label>
<input type="number" name="budget_depense" id="budget_depense" class="form-control" required>
</div>
<div class="mb-2">
<label class="form-label">Motif de différence</label>
<textarea class="form-control" name="motif" rows="3"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-success">
<i class="bi bi-save"></i> Enregistrer
</button>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,64 @@
{% extends "BASE.html" %}
{% load static %}
{% block 'titre_page' %} Gestion des projets {% endblock %}
{% block 'contenu' %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-{% if message.tags == "error" %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
<h5 class="mb-4 fw-bold text-uppercase text-orange-dark">
<i class="bi bi-kanban-fill me-2"></i> Suivi des Activités ({{ nom_projet }} )
</h5>
<div class="row d-flex justify-content-center mb-4">
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4">
<span class="fs-5">Budget Total (GNF)</span>
<h3>{{ budget_total }}</h3>
</div>
<div class="col text-white bg-info d-flex flex-column justify-content-center align-items-center border rounded mx-3 p-4">
<span class="fs-5">Budget RH (GNF)</span>
<h3>{{ budget_RH }}</h3>
</div>
<div class="col text-white bg-warning d-flex flex-column justify-content-center align-items-center border rounded p-4">
<span class="fs-5">Budget Dépensé (GNF)</span>
<h3>{{budget_depense}}</h3>
</div>
</div>
<div class="row">
<div class="mt-4 d-flex justify-content-between mb-3">
<h5><i class="bi bi-people"></i> La liste des Activités</h5>
<div>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalAjoutActivite">
<i class="bi bi-plus-circle me-2"></i> Ajouter une Activité
</button>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalAjoutDocument">
<i class="bi bi-plus-circle me-2"></i> Ajouter un document
</button>
<button class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#modalListeDocument">
Documents du projet
</button>
</div>
</div>
</div>
<div class="table-responsive">
<div id="tableau-liste-activite" data-urllisteactivite="{% url 'gestion_projet:liste-activites-projet' %}"></div>
</div>
{% endblock %}
{% block 'modal' %}
{% include "gestion_projet/parts/modalAjoutActivite.html" %}
{% include "gestion_projet/parts/modalDetailActivite.html" %}
{% include "gestion_projet/parts/modalAjoutDocument.html" %}
{% include "gestion_projet/parts/modalMiseAJourDepense.html" %}
{% include "gestion_projet/parts/modalAjoutLivrable.html" %}
{% include "gestion_projet/parts/liste_document_projet.html" %}
{% include "gestion_projet/parts/modalAnnulerActivite.html" %}
{% endblock %}
{% block 'js' %}
<script type="text/javascript" src="{% static 'gestion_projet/js/suivi-activites.js' %}"></script>
<script type="text/javascript" src="{% static 'gestion_projet/js/liste_documents_projet.js' %}"></script>
{% endblock %}

3
gestion_projet/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -335,7 +335,6 @@ def activites_projet(request):
} }
return render(request, 'gestion_projet/suivi_activite.html', context) return render(request, 'gestion_projet/suivi_activite.html', context)
@login_required @login_required
def ajouter_activite_projet(request): def ajouter_activite_projet(request):
"""Vue pour ajouter une activité à un projet spécifique via un formulaire""" """Vue pour ajouter une activité à un projet spécifique via un formulaire"""

View File

3
gestion_salle/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
gestion_salle/apps.py Normal file
View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class GestionSalleConfig(AppConfig):
name = 'gestion_salle'

32
gestion_salle/forms.py Normal file
View File

@@ -0,0 +1,32 @@
from django import forms
from .models import Reservation
class ReservationForm(forms.ModelForm):
class Meta:
model = Reservation
fields = ['salle', 'date_debut', 'date_fin', 'heure_debut', 'heure_fin', 'motif_reservation', 'besoin_zoom', 'besoin_ordi']
widgets = {
'date_debut': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'date_fin': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'heure_debut': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
'heure_fin': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
'motif_reservation': forms.Textarea(attrs={'rows': 3, 'cols': 40, 'style':'resize:none;', 'class': 'form-control'}),
'salle': forms.Select(attrs={'class': 'form-select'}),
}
besoin_zoom = forms.BooleanField(
required=False,
label="Besoin d'un lien Zoom ?",
widget=forms.CheckboxInput(attrs={'class': 'form-check-input', 'id': 'id_besoin_zoom'})
)
besoin_ordi = forms.BooleanField(
required=False,
label="Besoin d'ordinateur ?",
widget=forms.CheckboxInput(attrs={'class': 'form-check-input', 'id': 'id_besoin_ordi'})
)
class RefusReservationForm(forms.Form):
motif_refus = forms.CharField(
label= "Motif du refus",
widget=forms.Textarea(attrs={'rows': 3, 'cols': 40, 'style': 'resize:none;'}),
required=True
)

View File

@@ -0,0 +1,34 @@
# Generated by Django 5.2.13 on 2026-04-17 12:03
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('gestion_employe', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Reservation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('salle', models.CharField(choices=[('formation', 'Salle de formation'), ('reunion', 'Salle de réunion'), ('lien_zoom', 'Lien Zoom')], max_length=100)),
('date_demande', models.DateTimeField(auto_now_add=True)),
('date_debut', models.DateField()),
('date_fin', models.DateField(blank=True, null=True)),
('heure_debut', models.TimeField()),
('heure_fin', models.TimeField()),
('besoin_zoom', models.BooleanField(default=False, verbose_name="Besoin d'un lien Zoom ?")),
('besoin_ordi', models.BooleanField(default=False, verbose_name="Besoin d'un ordinateur ?")),
('lien_zoom', models.URLField(blank=True, null=True, verbose_name='Lien Zoom')),
('motif_reservation', models.TextField()),
('statut', models.CharField(choices=[('en_attente', 'En attente'), ('validee', 'Validée'), ('refusee', 'Refusée'), ('annulee', 'Annulée')], default='en_attente', max_length=25)),
('employe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_employe.employe')),
],
),
]

View File

View File

@@ -15,6 +15,10 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
currentReservationId = data.id_reservation; currentReservationId = data.id_reservation;
<<<<<<< HEAD
=======
console.log(data);
>>>>>>> c28b14f (clean: remove pycache from tracking)
$("id_reservation_detail").value = data.id_reservation; $("id_reservation_detail").value = data.id_reservation;
$("id_reservation_refus").value = data.id_reservation; $("id_reservation_refus").value = data.id_reservation;
$("id_reservation_zoom").value = data.id_reservation; $("id_reservation_zoom").value = data.id_reservation;
@@ -29,10 +33,13 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
$("besoin_ordinateur").checked=data.besoin_ordinateur; $("besoin_ordinateur").checked=data.besoin_ordinateur;
$("lien_zoom").value=data.lien_zoom; $("lien_zoom").value=data.lien_zoom;
<<<<<<< HEAD
if (data.besoin_zoom === false){ if (data.besoin_zoom === false){
$("lien_zoom_container").className = "d-none"; $("lien_zoom_container").className = "d-none";
} }
=======
>>>>>>> c28b14f (clean: remove pycache from tracking)
if(data.statut !== "annulee"){ if(data.statut !== "annulee"){
$("motif_refus_container").className = "d-none"; $("motif_refus_container").className = "d-none";
}else{ }else{
@@ -170,9 +177,15 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("lien_zoom_container").className = 'd-none'; $("lien_zoom_container").className = 'd-none';
} }
<<<<<<< HEAD
// if(data.statut !== "refusee"){ // if(data.statut !== "refusee"){
// $("motif_refus_container").className = 'd-none'; // $("motif_refus_container").className = 'd-none';
// } // }
=======
if(data.statut !== "refusee"){
$("motif_refus_container").className = 'd-none';
}
>>>>>>> c28b14f (clean: remove pycache from tracking)
$("id_reservation_detail").value = data.id; $("id_reservation_detail").value = data.id;
$("id_reservation_refus").value = data.id; $("id_reservation_refus").value = data.id;
@@ -181,15 +194,23 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("employe").value=data.employe; $("employe").value=data.employe;
$("salle").value=data.salle; $("salle").value=data.salle;
$("statut-reservation").innerHTML=data.statut; $("statut-reservation").innerHTML=data.statut;
<<<<<<< HEAD
$("date_debut").value = data.date_debut; $("date_debut").value = data.date_debut;
$("date_fin").value = data.date_fin; $("date_fin").value = data.date_fin;
=======
$("date_evenement").value=data.date_debut;
>>>>>>> c28b14f (clean: remove pycache from tracking)
$("heure_debut").value=data.heure_debut; $("heure_debut").value=data.heure_debut;
$("heure_fin").value=data.heure_fin; $("heure_fin").value=data.heure_fin;
$("motif_reservation").value=data.motif_reservation; $("motif_reservation").value=data.motif_reservation;
$("besoin_zoom").checked=data.besoin_zoom; $("besoin_zoom").checked=data.besoin_zoom;
$("besoin_ordinateur").checked=data.besoin_ordi; $("besoin_ordinateur").checked=data.besoin_ordi;
$("lien_zoom").value=data.lien_zoom; $("lien_zoom").value=data.lien_zoom;
<<<<<<< HEAD
// $("motif_refus").value=data.motif_refus; // $("motif_refus").value=data.motif_refus;
=======
$("motif_refus").value=data.motif_refus;
>>>>>>> c28b14f (clean: remove pycache from tracking)
const modal = new bootstrap.Modal($("modalDetailReservation")); const modal = new bootstrap.Modal($("modalDetailReservation"));
bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide(); bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();

View File

@@ -0,0 +1,59 @@
{% extends "BASE.html" %}
{% load static %}
{% block 'titre_page' %} Gestion des projets {% endblock %}
{% block 'css' %}
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@calendarjs/ce/dist/style.min.css" />
{% endblock %}
{% block 'contenu' %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-{% if message.tags == "error" %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
<div class="row">
<div class="col py-2 px-4">
<h1 class="text-center">Reservation de salle</h1>
<hr>
<div class="row d-flex justify-content-around">
<div class="form-group col-5 me-2">
<label>Selectionner le jour concerné :</label>
<input type="date" class="form-control" id="semaineDate" />
</div>
<div class="form-group col-5 me-2">
<label>Selectionner une salle :</label>
<select class = "form-select" id="liste-salle">
<option value='formation'>Salle de formation</option>
<option value='reunion'>Salle de réunion</option>
<option value='lien_zoom'>Lien Zoom</option>
</select>
</div>
</div>
<div class="row mt-2">
<div class="col d-flex justify-content-center">
<button class="btn btn-lg btn-primary me-2" data-bs-toggle="modal" data-bs-target="#modalReservation">Nouvelle reservation</button>
<button type="button" class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#modalReservationAttente">
Validation en attente <span class="badge badge-light">{{ nb_reservation_attente }}</span>
</button>
</div>
</div>
<hr>
<div data-url="{% url 'gestion_salle:liste-reservation' %}" id="planning-reservation"></div>
</div>
</div>
{% endblock %}
{% block 'modal' %}
{% include 'gestion_salle/parts/modalCreationReservation.html' %}
{% include 'gestion_salle/parts/ModaleAjoutLienZoom.html' %}
{% include 'gestion_salle/parts/ModalRefusReservation.html' %}
{% include 'gestion_salle/parts/modalDetailResevation.html' %}
{% include 'gestion_salle/parts/modalListeValidation.html' %}
{% endblock %}
{% block 'js' %}
<script src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@calendarjs/ce/dist/index.min.js"></script>
<script src="{% static 'gestion_salle/js/index.js' %}"></script>
{% endblock %}

View File

@@ -0,0 +1,22 @@
<div class="modal fade" id="refusModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header ">
<h5 class="modal-title">Motif du refus</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form method="POST" id="refusForm" action="{% url 'gestion_salle:refuser-reservation' %}">
{% csrf_token %}
<input type='' class="form-control" id="id_reservation_refus" name='id_reservation' value="">
<textarea class="form-control" name="motif_refus" rows="3" required></textarea>
<div class="modal-footer">
<button type="submit" class="btn btn-danger">Refuser</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,26 @@
<!-- Modal Ajouter/Modifier lien Zoom -->
<div class="modal fade" id="modalZoom" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header ">
<h5 class="modal-title">Ajouter/Modifier le lien Zoom</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form method="post" id="formZoom" action="{% url 'gestion_salle:ajouter-lien_zoom' %}">
{% csrf_token %}
<input type='hidden' class="form-control" id="id_reservation_zoom" name='id_reservation'>
<div class="mb-3">
<label for="lienZoom" class="form-label">Lien Zoom</label>
<input type="url" class="form-control" name="lien_zoom" id="lienZoom"
value="" required>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-success">Enregistrer</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,23 @@
<!-- Modal de création d'une nouvelle reservation -->
<div class="modal fade" id="modalReservation" tabindex="-1" aria-labelledby="modalReservationLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="modalReservationLabel">
Nouvelle reservation
</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form method="POST" action="{% url 'gestion_salle:reservation-salle' %}">
{% csrf_token %}
{{ formulaire_reservation.as_p }}
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
<button type="submit" class="btn btn-primary">Enregistrer</button>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,16 @@
<!-- Modal d'affichage des détails d'une reservation -->
<div class="modal fade" id="modalReservationAttente" tabindex="-1" aria-labelledby="modalReservationAttenteLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="modalReservationAttenteLabel">
Liste des reservation en attente de validation
</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="tableau-reservation-attente" data-reservationattentes = "{% url 'gestion_salle:liste-reservation-attente' %}"></div>
</div>
</div>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More