2 Commits

Author SHA1 Message Date
41dcb84987 Merge branch 'main' into feature/suppression_reservation
All checks were successful
Organisation/sirh/pipeline/pr-main This commit looks good
2026-10-08 12:43:44 +00:00
9fb079c9a7 Ajout de la suppression de la reservation de salle
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Organisation/sirh/pipeline/pr-main There was a failure building this commit
2026-10-07 09:42:34 +00:00
10 changed files with 159 additions and 167 deletions

View File

@@ -14,20 +14,6 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
responsiveLayout : "hide"
})
const tableauToutesAffectations = new Tabulator("#tableauToutesAffectations", {
layout: "fitColumns",
pagination: "local",
paginationSize: 10,
placeholder: "Aucune affectation enregistrée pour cet employé.",
columns: [
{title: "Projet", field: "projet"},
{title: "Rôle", field: "role"},
{title: "Pourcentage", field: "pourcentage_temps_affectation", formatter: cell => `${cell.getValue()} %`},
{title: "Date de début", field: "date_affectation"},
{title: "Date de fin", field: "date_fin_daffectation", formatter: cell => cell.getValue() || "Non définie"},
],
})
fetch(url_liste_employe)
.then(response => response.json())
.then(data => {
@@ -163,9 +149,6 @@ tableau_liste_employe.on("rowClick", function (row, rowData) {
document.getElementById("affectation-nom-employe").textContent = data.employe || "Employé inconnu";
document.getElementById("affecter_employe_id").value = data.id;
$("liste-affectations-nom-employe").textContent = data.employe || "Employé inconnu";
tableauToutesAffectations.setData(data.toutes_affectations);
document.getElementById("affectations-list").innerHTML = "";
for (const affectation of data.affectations) {
document.getElementById("affectations-list").innerHTML += `
@@ -222,23 +205,6 @@ tableau_liste_employe.on("rowClick", function (row, rowData) {
modal.show();
})
$("btnAfficherToutesAffectations").addEventListener("click", function() {
const modalDetail = $("modalDetailEmploye");
const modalAffectations = $("modalListeAffectations");
modalDetail.addEventListener("hidden.bs.modal", function ouvrirListe() {
modalDetail.removeEventListener("hidden.bs.modal", ouvrirListe);
bootstrap.Modal.getOrCreateInstance(modalAffectations).show();
}, {once: true});
modalAffectations.addEventListener("hidden.bs.modal", function revenirAuDetail() {
modalAffectations.removeEventListener("hidden.bs.modal", revenirAuDetail);
bootstrap.Modal.getOrCreateInstance(modalDetail).show();
}, {once: true});
bootstrap.Modal.getOrCreateInstance(modalDetail).hide();
})
$("enregistrerDetail").addEventListener("click", function() {
const id_ = document.getElementById('detail-id').value;
const fonction = document.getElementById('detail-fonction').value;

View File

@@ -61,7 +61,6 @@
{% include "gestion_employe/parts/modalAffectationProjet.html" %}
{% include "gestion_employe/parts/modalDocument.html" %}
{% include "gestion_employe/parts/modalListeContratExpirants.html" %}
{% include "gestion_employe/parts/modalListeAffectations.html" %}
{% endblock %}
{% block 'js' %}
<script type="text/javascript" src="{% static 'gestion_employe/js/index.js' %}"></script>

View File

@@ -112,16 +112,11 @@
<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>
<div class="d-flex gap-2">
<button type="button" class="btn btn-outline-primary" id="btnAfficherToutesAffectations">
<i class="bi bi-list-ul"></i> Voir toutes les affectations
{% 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>
{% 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>
{% endif %}
</div>
<div class="row">
<div id="affectations-list"></div>

View File

@@ -1,20 +0,0 @@
<div class="modal fade" id="modalListeAffectations" tabindex="-1" aria-labelledby="modalListeAffectationsLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalListeAffectationsLabel">
Toutes les affectations de <span id="liste-affectations-nom-employe"></span>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
</div>
<div class="modal-body">
<div id="tableauToutesAffectations"></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

@@ -418,16 +418,6 @@ def liste_employe(request):
date_fin_daffectation__gte=timezone.now().date()
)
]
toutes_affectations = [
{
"projet": affectation.projet.nom_projet,
"role": dict(Affectation.ROLE_CHOICES).get(affectation.role, affectation.role),
"pourcentage_temps_affectation": affectation.pourcentage_temps_affectation,
"date_affectation": affectation.date_affectation,
"date_fin_daffectation": affectation.date_fin_daffectation,
}
for affectation in Affectation.objects.filter(employe=employe).select_related("projet")
]
data.append(
{
@@ -437,7 +427,6 @@ def liste_employe(request):
"email": employe.user.email,
"formations": formations,
"affectations": affectations,
"toutes_affectations": toutes_affectations,
"projet": projets,
"contrats": contrats,
"departement": employe.departement.id if employe.departement else "",

View File

@@ -2,7 +2,71 @@ const $ = (element) => document.getElementById(element);
const { Schedule } = calendarjs;
let dateAUtiliser = new Date().toISOString().split('T')[0];
let currentReservationId = null;
function afficherDetailsReservation(data) {
const reservationId = data.id_reservation || data.id;
$("id_reservation_detail").value = reservationId;
$("id_reservation_refus").value = reservationId;
$("id_reservation_zoom").value = reservationId;
$("employe").value = data.employe;
$("salle").value = data.salle;
$("statut-reservation").textContent = data.statut;
$("date_evenement").value = data.date_evenement || data.date_debut;
$("heure_debut").value = data.heure_debut;
$("heure_fin").value = data.heure_fin;
$("motif_reservation").value = data.motif_reservation;
$("besoin_zoom").checked = data.besoin_zoom;
$("besoin_ordinateur").checked = data.besoin_ordinateur ?? data.besoin_ordi;
$("lien_zoom").value = data.lien_zoom || "";
$("lien_zoom_container").classList.toggle("d-none", !data.besoin_zoom);
const currentUserId = $("current-user-id").dataset.userId;
const isOwner = data.est_proprietaire === true
|| String(data.employe_id) === currentUserId;
const isPending = data.est_en_attente === true
|| data.statut === "En attente"
|| data.statut === "en_attente";
$("bouton-supprimer-reservation").classList.toggle(
"d-none",
!(isOwner && isPending)
);
for (const id of ["refuserReservation", "refuserReservationInfo", "bouton-valider", "bouton-valider-info"]) {
const button = $(id);
if (button) {
button.style.display = isPending && !isOwner ? "inline-block" : "none";
}
}
}
function supprimerReservation(reservationId) {
if (!window.confirm("Voulez-vous vraiment supprimer cette demande de réservation en attente ?")) {
return;
}
const form = $("formulaire-details");
const csrf = form.querySelector("[name=csrfmiddlewaretoken]").value;
const url = $("bouton-supprimer-reservation").dataset.url.replace(
"/0/",
`/${reservationId}/`
);
fetch(url, {
method: "POST",
headers: {"X-CSRFToken": csrf},
})
.then(response => response.json().then(data => ({response, data})))
.then(({response, data}) => {
window.alert(data.message);
if (response.ok && data.success) {
window.location.reload();
}
})
.catch(() => {
window.alert("La suppression a échoué. Veuillez réessayer.");
});
}
const calendrier = Schedule(document.getElementById('planning-reservation'), {
type: 'weekdays',
@@ -19,31 +83,7 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
return response.json();
})
.then(data => {
currentReservationId = data.id_reservation;
$("id_reservation_detail").value = data.id_reservation;
$("id_reservation_refus").value = data.id_reservation;
$("id_reservation_zoom").value = data.id_reservation;
$("employe").value=data.employe;
$("salle").value=data.salle;
$("statut-reservation").innerHTML=data.statut;
$("date_evenement").value=data.date_evenement;
$("heure_debut").value=data.heure_debut;
$("heure_fin").value=data.heure_fin;
$("motif_reservation").value=data.motif_reservation;
$("besoin_zoom").checked=data.besoin_zoom;
$("besoin_ordinateur").checked=data.besoin_ordinateur;
$("lien_zoom").value=data.lien_zoom;
const zoomContainer = $("lien_zoom_container");
if (!data.besoin_zoom) {
zoomContainer.classList.add("d-none");
} else {
zoomContainer.classList.remove("d-none");
}
if (data.besoin_zoom === false){
$("lien_zoom_container").className = "d-none";
}
afficherDetailsReservation(data);
})
.catch(error => console.error("Impossible de charger les détails de la réservation :", error));
}
@@ -58,6 +98,10 @@ $('semaineDate').addEventListener('change', () => {
calendrier.render();
})
$("bouton-supprimer-reservation").addEventListener("click", () => {
supprimerReservation($("id_reservation_detail").value);
});
evenement_defini = null
$("liste-salle").addEventListener("change", (e) => {
@@ -76,39 +120,22 @@ $("liste-salle").addEventListener("change", (e) => {
function chargement_evenement(){
const url = $("planning-reservation").dataset.url;
fetch (url)
.then(response => response.json())
.then(response => {
if (!response.ok) {
throw new Error(`Erreur HTTP ${response.status}`);
}
return response.json();
})
.then(data => {
calendrier.setData(data);
})
.catch(error => console.error("Impossible de charger les réservations :", error));
}
document.addEventListener("DOMContentLoaded", function () {
chargement_evenement()
})
$("bouton-annuler").addEventListener("click", (e) => {
e.preventDefault();
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
const url_annuler = $("formulaire-details").dataset.urlannuler;
e.preventDefault();
fetch(
url_annuler,
{
method: "POST",
headers: {
"X-Requested-With": "XMTHttpRequest",
"X-CSRFToken": csrf
},
body: new FormData($("formulaire-details"))
}
)
.then(response => response.json())
.then(data => console.log(data))
})
if($("bouton-valider")){
$("bouton-valider").addEventListener("click", (e) => {
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
@@ -180,40 +207,7 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente"
tableau_reservation_attente.on("rowClick", (row, rowData) => {
const data = rowData.getData();
console.log(data);
if(data.besoin_zoom === false){
$("lien_zoom_container").className = 'd-none';
}
const id_user = $("current-user-id").dataset.userId;
$("id_reservation_detail").value = data.id;
$("id_reservation_refus").value = data.id;
$("id_reservation_zoom").value = data.id;
$("employe").value=data.employe;
$("salle").value=data.salle;
$("statut-reservation").innerHTML=data.statut;
$("date_evenement").value = data.date_evenement;
$("heure_debut").value=data.heure_debut;
$("heure_fin").value=data.heure_fin;
$("motif_reservation").value=data.motif_reservation;
$("besoin_zoom").checked=data.besoin_zoom;
$("besoin_ordinateur").checked=data.besoin_ordi;
$("lien_zoom").value=data.lien_zoom;
const refuserReservation = document.querySelector("#refuserReservation");
const bouton_valider = document.querySelector("#bouton-valider");
if((refuserReservation | bouton_valider) && data.statut === "En attente"){
refuserReservation.style.display = "inline-block";
bouton_valider.style.display = "inline-block";
}
if (id_user != data.employe_id){
$("bouton-annuler").className = "d-none";
}
afficherDetailsReservation(data);
const modal = new bootstrap.Modal($("modalDetailReservation"));
bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();

View File

@@ -55,5 +55,5 @@
{% 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' %}?v=3"></script>
<script src="{% static 'gestion_salle/js/index.js' %}?v=5"></script>
{% endblock %}

View File

@@ -10,7 +10,7 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form method="POST" data-urlannuler="{% url 'gestion_salle:annuler-reservation' %}" data-urlvalider="{% url 'gestion_salle:valider-reservation' %}" id="formulaire-details">
<form method="POST" data-urlvalider="{% url 'gestion_salle:valider-reservation' %}" id="formulaire-details">
{% csrf_token %}
<input type='' class="form-control" id="id_reservation_detail" name='id_reservation' hidden>
<div class="form-group mb-2">
@@ -63,7 +63,18 @@
<button class="btn btn-success me-1" id="bouton-valider-info">Valider</button>
{% endif %}
<span id="current-user-id" data-user-id="{{ user.id }}"></span>
<button class="btn btn-danger" id="bouton-annuler">Annuler</button>
<button
type="button"
class="btn btn-outline-danger d-none"
id="bouton-supprimer-reservation"
data-url="{% url 'gestion_salle:supprimer-reservation' 0 %}"
>
Supprimer ma demande
</button>
<button type="button" class="btn btn-secondary" id="bouton-annuler" data-bs-dismiss="modal">
Fermer
</button>
</div>
</form>
</div>

View File

@@ -45,5 +45,10 @@ urlpatterns = [
'liste-reservation-attente',
views.liste_reservation_attente,
name="liste-reservation-attente"
),
path(
'reservation/<int:reservation_id>/supprimer',
views.supprimer_reservation,
name="supprimer-reservation",
)
]

View File

@@ -125,19 +125,25 @@ Veuillez vous connecter à la plateforme pour traiter cette demande.""",
return render(request, "gestion_salle/index.html", context)
@login_required
def liste_reservation(request:HttpRequest):
"""Vue d'affichage des creneaux disponibles"""
reservations = Reservation.objects.filter(statut = "validee")
try:
employe = Employe.objects.get(user=request.user)
reservations = Reservation.objects.filter(
Q(statut="validee")
| Q(statut="en_attente", employe=employe)
)
except Employe.DoesNotExist:
reservations = Reservation.objects.filter(statut="validee")
liste_reservation = []
for reservation in reservations:
color = None
if reservation.statut == "en_attente":
color = "#ffc107"
elif reservation.statut == "validee":
color = "#198754"
else:
color = "#dc3545"
color = "#198754"
liste_reservation.append({
"guid": reservation.pk,
@@ -146,6 +152,7 @@ def liste_reservation(request:HttpRequest):
"start": reservation.heure_debut,
"end": reservation.heure_fin,
"color": color,
"statut": dict(Reservation.STATUT).get(reservation.statut),
})
return JsonResponse(liste_reservation, safe=False)
@@ -183,6 +190,7 @@ def liste_reservation_attente(request):
liste_reservation = [
{
**model_to_dict(reservation),
"id": reservation.pk,
"employe": f"{reservation.employe.user.first_name} {reservation.employe.user.last_name}",
"employe_id": reservation.employe.user.id,
"salle": dict(Reservation.TYPE_CHOICES).get(reservation.salle),
@@ -193,6 +201,49 @@ def liste_reservation_attente(request):
return JsonResponse(liste_reservation, safe=False)
@login_required
def supprimer_reservation(request: HttpRequest, reservation_id: int):
if request.method != "POST":
return JsonResponse(
{"success": False, "message": "Méthode non autorisée."},
status=405,
)
try:
employe = Employe.objects.get(user=request.user)
except Employe.DoesNotExist:
return JsonResponse(
{"success": False, "message": "Profil employé introuvable."},
status=403,
)
try:
reservation = Reservation.objects.get(
pk=reservation_id,
employe=employe,
)
except Reservation.DoesNotExist:
return JsonResponse(
{"success": False, "message": "Réservation introuvable ou accès non autorisé."},
status=404,
)
if reservation.statut != "en_attente":
return JsonResponse(
{
"success": False,
"message": "Seules les réservations en attente peuvent être supprimées.",
},
status=400,
)
reservation.delete()
return JsonResponse(
{"success": True, "message": "Votre réservation en attente a été supprimée."}
)
def detail_reservation(request:HttpRequest, reservation_id:int):
reservation = Reservation.objects.get(id=reservation_id)
employe = reservation.employe.user
@@ -209,6 +260,8 @@ def detail_reservation(request:HttpRequest, reservation_id:int):
'besoin_zoom': reservation.besoin_zoom,
'besoin_ordinateur': reservation.besoin_ordi,
'lien_zoom': reservation.lien_zoom or '',
'est_proprietaire': request.user.is_authenticated and reservation.employe.user_id == request.user.id,
'est_en_attente': reservation.statut == "en_attente",
}
return JsonResponse(reservation_json, safe=True)