Compare commits
7 Commits
message_co
...
41dcb84987
| Author | SHA1 | Date | |
|---|---|---|---|
| 41dcb84987 | |||
|
|
0abbb26149 | ||
| 9fb079c9a7 | |||
| f061f482d9 | |||
| 622b412a21 | |||
| 54dc65f0d8 | |||
|
|
29a42ee6be |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -15,6 +15,21 @@ try:
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
EMAIL_EQUIPE_INFORMATIQUE = list(
|
||||
Employe.objects.filter(departement__nom="Systeme informatique").values_list('user__email', flat=True)
|
||||
) or ["support@cerfig.org"]
|
||||
except:
|
||||
EMAIL_EQUIPE_INFORMATIQUE = ["support@cerfig.org"]
|
||||
|
||||
def est_equipe_informatique(employe):
|
||||
"""Vérifie si un employé appartient au département informatique"""
|
||||
return employe.departement and employe.departement.nom == "Systeme informatique"
|
||||
|
||||
def est_assistante_direction(employe):
|
||||
"""Vérifie si un employé est l'assistante de direction"""
|
||||
return employe.fonction == "assistant_direction"
|
||||
|
||||
def solde_conge(employe):
|
||||
"""Fonction de calcul du solde de congé restant l'employé"""
|
||||
contrat = Contrat.objects.filter(employe=employe, statut='actif').order_by('-date_debut').first()
|
||||
@@ -58,4 +73,4 @@ def envoyer_mail(sujet, message, destinataires):
|
||||
settings.EMAIL_HOST_USER,
|
||||
destinataires,
|
||||
fail_silently=False,
|
||||
)
|
||||
)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -9,7 +9,6 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
|
||||
{"title": "Projet", "field": "projet"},
|
||||
{"title": "Téléphone", "field": "telephone"},
|
||||
],
|
||||
// ajaxURL: url_liste_employe,
|
||||
pagination: true,
|
||||
paginationSize: 10,
|
||||
responsiveLayout : "hide"
|
||||
@@ -19,7 +18,6 @@ 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);
|
||||
@@ -29,7 +27,6 @@ fetch(url_liste_employe)
|
||||
|
||||
tableau_liste_employe.on("rowClick", function (row, rowData) {
|
||||
const data = rowData.getData();
|
||||
console.log('Employé cliqué:', data);
|
||||
|
||||
document.getElementById('detail-id').value = data.id;
|
||||
document.getElementById('detail-matricule').value = data.matricule;
|
||||
@@ -215,17 +212,10 @@ $("enregistrerDetail").addEventListener("click", function() {
|
||||
const matricule = document.getElementById('detail-matricule').value;
|
||||
const departement = document.getElementById('detail-departement').value;
|
||||
|
||||
console.log('=== ENREGISTREMENT DÉTAILS ===');
|
||||
console.log('ID:', id_);
|
||||
console.log('Fonction:', fonction);
|
||||
console.log('Date embauche:', dateEmbauche);
|
||||
console.log('Matricule:', matricule);
|
||||
console.log('Département (valeur brute):', departement);
|
||||
console.log('Département (type):', typeof departement);
|
||||
console.log('Département est vide:', !departement);
|
||||
|
||||
const url_enregistrer_detail = $("modalDetailEmployeBody").dataset.url;
|
||||
const url_enregistrer_detail = document.getElementById("modalDetailEmployeBody").dataset.url;
|
||||
|
||||
console.log("Le lien de l'enregistrement :", document.getElementById("modalDetailEmployeBody").dataset)
|
||||
|
||||
const payload = {
|
||||
"id": id_,
|
||||
"fonction": fonction,
|
||||
@@ -234,9 +224,6 @@ $("enregistrerDetail").addEventListener("click", function() {
|
||||
"departement": departement
|
||||
};
|
||||
|
||||
console.log('Payload JSON:', payload);
|
||||
console.log('Payload stringifié:', JSON.stringify(payload));
|
||||
|
||||
fetch(url_enregistrer_detail, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -283,15 +270,12 @@ const listeContratExpirant = new Tabulator("#listeContratExpirant", {
|
||||
ajaxURL: $("boutonContratExpirants").dataset.urlexpirants,
|
||||
})
|
||||
|
||||
let tabContrats = null;
|
||||
/* let tabContrats = null;
|
||||
|
||||
document.getElementById("modalContratsEmploye")
|
||||
.addEventListener("show.bs.modal", function () {
|
||||
document.getElementById("modalContratsEmploye").addEventListener("show.bs.modal", function () {
|
||||
|
||||
const employeId = document.getElementById("detail-id").value;
|
||||
|
||||
console.log("Employe ID :", employeId);
|
||||
|
||||
if (!employeId) {
|
||||
alert("Aucun employé sélectionné");
|
||||
return;
|
||||
@@ -350,4 +334,4 @@ document.getElementById("modalContratsEmploye")
|
||||
.catch(error => {
|
||||
console.error("Erreur :", error);
|
||||
});
|
||||
});
|
||||
}); */
|
||||
|
||||
@@ -64,4 +64,4 @@
|
||||
{% endblock %}
|
||||
{% block 'js' %}
|
||||
<script type="text/javascript" src="{% static 'gestion_employe/js/index.js' %}"></script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -12,27 +12,22 @@
|
||||
</div>
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert">
|
||||
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %} alert-dismissible fade show mt-2" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if not expiration_contrat %} <div class="alert alert-danger fade show alert-dismissible mt-2"> <strong>Important :</strong>
|
||||
Les informations sur votre contrat n'ont pas été renseignées,
|
||||
veuillez contacter les ressources humaines. </div>
|
||||
|
||||
{% elif contrat_nb_jours_restant > 0 %} <div class="alert alert-danger fade show alert-dismissible mt-2"> <strong>Important :</strong>
|
||||
Votre contrat de travail expire dans <strong>{{ contrat_nb_jours_restant }} jour{{ contrat_nb_jours_restant|pluralize }}</strong>,
|
||||
veuillez contacter les ressources humaines. </div>
|
||||
{% if not has_contrat %}
|
||||
<div class="alert alert-danger mt-2">
|
||||
<strong>Important :</strong> Les informations sur votre contrat n'ont pas été renseignées, veuillez contacter les ressources humaines.
|
||||
</div>
|
||||
|
||||
{% elif contrat_nb_jours_restant == 0 %} <div class="alert alert-danger fade show alert-dismissible mt-2"> <strong>Important :</strong>
|
||||
Votre contrat de travail <strong>expire aujourd'hui</strong>.
|
||||
Veuillez contacter les ressources humaines. </div>
|
||||
|
||||
{% elif contrat_nb_jours_restant < 0 %} <div class="alert alert-danger fade show alert-dismissible mt-2"> <strong>Important :</strong>
|
||||
Votre contrat de travail est <strong>déjà expiré</strong>.
|
||||
Veuillez contacter les ressources humaines. </div>
|
||||
{% elif expiration_contrat %}
|
||||
<div class="alert alert-warning mt-2">
|
||||
<strong>Important :</strong> Votre contrat de travail expire dans {{ contrat_nb_jours_restant }} jours, veuillez contacter les ressources humaines.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="accordion mt-2" id="accordionInformationEmploye">
|
||||
@@ -132,7 +127,7 @@ Veuillez contacter les ressources humaines. </div>
|
||||
</div>
|
||||
<div class="form-group mb-2">
|
||||
<label>Statut :</label>
|
||||
<input type="text" class="form-control" value="{{ contrat.statut }}" id="statut" readonly>
|
||||
<input type="text" class="form-control" value="{{ contrat.statut }}" id="satut" readonly>
|
||||
</div>
|
||||
<div class="form-group mb-2">
|
||||
<label>fichier :</label>
|
||||
|
||||
@@ -25,4 +25,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,4 +54,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body" id="modalDetailEmployeBody">
|
||||
<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">
|
||||
@@ -135,4 +135,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Binary file not shown.
@@ -91,4 +91,4 @@ urlpatterns = [
|
||||
views.liste_contrat_expirants,
|
||||
name='liste-contrat-expirants'
|
||||
)
|
||||
]
|
||||
]
|
||||
|
||||
@@ -3,8 +3,8 @@ from datetime import datetime, timedelta
|
||||
|
||||
import requests
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from decouple import config
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import logout
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.password_validation import validate_password
|
||||
@@ -27,6 +27,18 @@ def index(request):
|
||||
if not request.user.groups.filter(name="ressource_humaine").exists():
|
||||
return redirect('gestion_employe:mon-profil')
|
||||
|
||||
response = requests.get(f"{config('SERVEUR_AUTHENTIFICATION')}recuperer-list-departement/")
|
||||
|
||||
if response.status_code == 200:
|
||||
try:
|
||||
departements_data = response.json()
|
||||
for dept in departements_data:
|
||||
Departement.objects.update_or_create(
|
||||
nom = dept['nom']
|
||||
)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"[ERROR] Erreur lors du décodage JSON : {e}")
|
||||
|
||||
employes = Employe.objects.all().order_by('-user__date_joined')
|
||||
nombre_employes = Employe.objects.count()
|
||||
nombre_cps = Contrat.objects.filter(type_contrat='contrat_prestation').count()
|
||||
@@ -341,9 +353,6 @@ def enregistrer_detail_employe(request):
|
||||
|
||||
# Traiter le département
|
||||
dept_value = data.get('departement')
|
||||
print(f"[DEBUG] Département brut: {dept_value!r}")
|
||||
print(f"[DEBUG] Type département: {type(dept_value).__name__}")
|
||||
print(f"[DEBUG] Est vide: {not dept_value}")
|
||||
|
||||
if dept_value and str(dept_value).strip():
|
||||
try:
|
||||
@@ -521,7 +530,7 @@ def modifier_mot_passe(request):
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
"http://localhost:8000/modifier-mot-de-passe/",
|
||||
f"{config('SERVEUR_AUTHENTIFICATION')}modifier-mot-de-passe/",
|
||||
json=donnees,
|
||||
headers=headers
|
||||
)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -22,7 +22,6 @@ class ProjetForm(forms.ModelForm):
|
||||
'date_fin',
|
||||
'numero_convention',
|
||||
'domaine_recherche',
|
||||
'type_projet',
|
||||
'budget',
|
||||
'budget_RH',
|
||||
'description'
|
||||
@@ -37,7 +36,6 @@ class ProjetForm(forms.ModelForm):
|
||||
'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"}),
|
||||
@@ -175,4 +173,4 @@ class LivrablesLivresForm(forms.ModelForm):
|
||||
'fichier': forms.ClearableFileInput(attrs={
|
||||
'class':'form-control',
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from datetime import datetime
|
||||
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
@@ -33,9 +31,11 @@ class DomaineDeRecherche(models.Model):
|
||||
|
||||
DOMAINE_RECHERCHE = (
|
||||
('sciences_sociales', 'Sciences sociales'),
|
||||
('naturelles', 'Naturelles'),
|
||||
('humaines', 'Humaines'),
|
||||
('veterinaires', 'Vétérinaires')
|
||||
('epidemiologie', 'Epidémiologie'),
|
||||
('sciences_biomedicales', 'Sciences biomédicales'),
|
||||
('service_pedagogique', 'Service Pédagogique'),
|
||||
('recherche_clinique', 'Recherche clinique')
|
||||
)
|
||||
|
||||
nom = models.CharField(
|
||||
@@ -54,13 +54,6 @@ class DomaineDeRecherche(models.Model):
|
||||
|
||||
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,
|
||||
@@ -86,12 +79,6 @@ class Projet(models.Model):
|
||||
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,
|
||||
@@ -114,13 +101,13 @@ class Projet(models.Model):
|
||||
|
||||
@property
|
||||
def statut(self):
|
||||
if self.date_fin < datetime.date.today():
|
||||
if self.date_fin < timezone.now().date():
|
||||
return "Terminé"
|
||||
return "En cours"
|
||||
|
||||
@property
|
||||
def avancement(self):
|
||||
aujourd_hui = datetime.date.today()
|
||||
aujourd_hui = timezone.now().date()
|
||||
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
|
||||
@@ -309,4 +296,4 @@ class LivrablesLivres(models.Model):
|
||||
null = True
|
||||
)
|
||||
def __str__(self):
|
||||
return self.nom
|
||||
return self.nom
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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,
|
||||
@@ -39,7 +40,6 @@ tableau_liste_projet.on("rowClick", function (row, rowData) {
|
||||
$("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;
|
||||
@@ -61,4 +61,4 @@ tableau_liste_projet.on("rowClick", function (row, rowData) {
|
||||
// $('detail-projet-form').addEventListener('submit', (e) => {
|
||||
// e.preventDefault();
|
||||
// new FormData($("detail-projet-form"));
|
||||
// })
|
||||
// })
|
||||
|
||||
@@ -18,4 +18,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,23 +31,15 @@
|
||||
<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>
|
||||
<option value="epidemiologie">Epidémiologie</option>
|
||||
<option value="sciences_biomedicales">Sciences biomédicales</option>
|
||||
<option value="service_pedagogique">Service Pédagogique</option>
|
||||
<option value="recherche_clinique">Recherche clinique</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group mb-2">
|
||||
@@ -112,4 +104,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -44,7 +44,6 @@ def liste_projet(request):
|
||||
"date_fin": p.date_fin,
|
||||
"numero_convention": p.numero_convention,
|
||||
"description": p.description,
|
||||
"type_projet": p.type_projet,
|
||||
"budget": p.budget,
|
||||
"budget_RH": p.budget_RH,
|
||||
"created_at": p.created_at,
|
||||
@@ -502,4 +501,4 @@ def annuler_activite_projet(request):
|
||||
activite.motif_annulation = request.POST.get("motif_annulation", "").strip()
|
||||
activite.save()
|
||||
messages.success(request, f"L'activité '{activite.titre}' a été annulée avec succès.")
|
||||
return redirect('gestion_projet:activites-projet')
|
||||
return redirect('gestion_projet:activites-projet')
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +1 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
|
||||
@@ -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,42 +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;
|
||||
|
||||
console.log("URL d'annulation :");
|
||||
console.log($("formulaire-details"))
|
||||
|
||||
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;
|
||||
@@ -183,42 +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;
|
||||
|
||||
console.log("Verification de data :", data);
|
||||
|
||||
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();
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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">
|
||||
@@ -50,19 +50,34 @@
|
||||
<input type="checkbox" class="form-check-input" id="besoin_ordinateur" readonly >
|
||||
</div>
|
||||
<hr />
|
||||
<div class='d-flex justify-content-around mt-2'>
|
||||
{% if appartient_au_departement_informatique %}
|
||||
<button class="btn btn-primary" id="ajoutZoom">Ajout du lien zoom</button>
|
||||
<div class='d-flex justify-content-center mt-2'>
|
||||
{% if est_equipe_informatique %}
|
||||
<button class="btn btn-primary me-1" id="ajoutZoom">Ajout du lien zoom</button>
|
||||
{% endif %}
|
||||
{% if appartient_direction %}
|
||||
<button class="btn btn-danger" id="refuserReservation" data-lienrefus="{% url 'gestion_salle:refuser-reservation' %}">Refuser</button>
|
||||
<button class="btn btn-success" id="bouton-valider">Valider</button>
|
||||
{% if est_assistante_direction %}
|
||||
<button class="btn btn-danger me-1" id="refuserReservation" data-lienrefus="{% url 'gestion_salle:refuser-reservation' %}">Refuser</button>
|
||||
<button class="btn btn-success me-1" id="bouton-valider">Valider</button>
|
||||
{% endif %}
|
||||
<span id="current-user-id" data-user-id="{{ request.user.id }}"></span>
|
||||
<button class="btn btn-danger" id="bouton-annuler">Annuler</button>
|
||||
{% if est_equipe_informatique %}
|
||||
<button class="btn btn-danger me-1" id="refuserReservationInfo" data-lienrefus="{% url 'gestion_salle:refuser-reservation' %}">Refuser</button>
|
||||
<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
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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",
|
||||
)
|
||||
]
|
||||
@@ -3,6 +3,7 @@ from datetime import timedelta
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Q
|
||||
from django.forms import model_to_dict
|
||||
from django.http import HttpRequest, JsonResponse
|
||||
from django.shortcuts import redirect, render
|
||||
@@ -16,13 +17,16 @@ from .models import Reservation
|
||||
|
||||
@login_required
|
||||
def index(request: HttpRequest):
|
||||
|
||||
"""Vue d'affichage de la page d'accueil de la gestion des salles"""
|
||||
try:
|
||||
employe = Employe.objects.get(user=request.user)
|
||||
except Employe.DoesNotExist:
|
||||
messages.error(request, "Profil employé introuvable.")
|
||||
return redirect('gestion_conges:conge')
|
||||
|
||||
est_equipe_info = fonctions_utilitaire.est_equipe_informatique(employe)
|
||||
est_assistante = fonctions_utilitaire.est_assistante_direction(employe)
|
||||
|
||||
if request.method == "POST":
|
||||
form = ReservationForm(request.POST)
|
||||
|
||||
@@ -37,11 +41,11 @@ def index(request: HttpRequest):
|
||||
besoin_ordi = form.cleaned_data['besoin_ordi']
|
||||
|
||||
if date_fin < date_debut:
|
||||
messages.error(request, "Date fin invalide.")
|
||||
messages.error(request, "La date de fin est antérieure à la date de début.")
|
||||
return redirect('gestion_salle:reservation-salle')
|
||||
|
||||
if heure_fin <= heure_debut:
|
||||
messages.error(request, "Heure invalide.")
|
||||
messages.error(request, "L'heure de fin est antérieure à l'heure de début.")
|
||||
return redirect('gestion_salle:reservation-salle')
|
||||
|
||||
if not request.user.first_name.strip() or not request.user.last_name.strip():
|
||||
@@ -54,8 +58,9 @@ def index(request: HttpRequest):
|
||||
created = []
|
||||
current_date = date_debut
|
||||
|
||||
while current_date <= date_fin:
|
||||
print(f"Création de réservations pour {employe}")
|
||||
|
||||
while current_date <= date_fin:
|
||||
reservation = Reservation.objects.create(
|
||||
employe=employe,
|
||||
date_debut=current_date,
|
||||
@@ -74,18 +79,35 @@ def index(request: HttpRequest):
|
||||
|
||||
messages.success(request, "Réservation(s) créée(s) avec succès.")
|
||||
|
||||
if fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION:
|
||||
# Nouvelle logique d'envoi de mails à la création :
|
||||
# 1. Une demande de salle (formation/réunion) est envoyée à l'assistante de direction pour validation.
|
||||
# - Si elle nécessite un lien Zoom ou un ordinateur, l'équipe informatique sera notifiée
|
||||
# seulement après la validation par l'assistante.
|
||||
# 2. Une demande de lien Zoom directement est envoyée à l'équipe informatique.
|
||||
if salle != "lien_zoom" and fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION:
|
||||
fonctions_utilitaire.envoyer_mail(
|
||||
sujet = "Reservation de salle",
|
||||
message = f"""
|
||||
sujet="Demande de réservation de salle",
|
||||
message=f"""Bonjour,
|
||||
Une nouvelle demande de réservation de la {dict(Reservation.TYPE_CHOICES).get(salle)} a été effectuée par {employe.user.first_name} {employe.user.last_name} du {form.cleaned_data.get('date_debut').strftime('%d/%m/%Y')} au {form.cleaned_data.get('date_fin').strftime('%d/%m/%Y')} pour motif "{motif}".
|
||||
{"Cette demande nécessite un lien Zoom et/ou un ordinateur et sera traitée par l'équipe informatique après votre validation." if besoin_zoom or besoin_ordi else ""}
|
||||
Veuillez vous connecter à la plateforme pour plus de détails.""",
|
||||
destinataires = fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION
|
||||
destinataires=fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION
|
||||
)
|
||||
|
||||
if salle == "lien_zoom" and fonctions_utilitaire.EMAIL_EQUIPE_INFORMATIQUE:
|
||||
fonctions_utilitaire.envoyer_mail(
|
||||
sujet="Demande de lien Zoom",
|
||||
message=f"""Bonjour,
|
||||
Une nouvelle demande de lien Zoom a été effectuée par {employe.user.first_name} {employe.user.last_name} du {date_debut.strftime('%d/%m/%Y')} au {date_fin.strftime('%d/%m/%Y')} de {heure_debut.strftime('%H:%M')} à {heure_fin.strftime('%H:%M')} pour le motif {motif}.
|
||||
|
||||
Veuillez vous connecter à la plateforme pour traiter cette demande.""",
|
||||
destinataires=fonctions_utilitaire.EMAIL_EQUIPE_INFORMATIQUE
|
||||
)
|
||||
return redirect('gestion_salle:reservation-salle')
|
||||
|
||||
|
||||
formulaire_reservation = ReservationForm()
|
||||
departement = employe.departement
|
||||
# departement = employe.departement
|
||||
|
||||
liste_demande_reservation = Reservation.objects.filter(
|
||||
employe=employe,
|
||||
@@ -95,29 +117,33 @@ Veuillez vous connecter à la plateforme pour plus de détails.""",
|
||||
context = {
|
||||
'formulaire_reservation': formulaire_reservation,
|
||||
'nb_reservation_attente': Reservation.objects.filter(statut='en_attente').count(),
|
||||
'appartient_au_departement_informatique': departement and departement.nom == "Systeme informatique",
|
||||
'appartient_direction': request.user.groups.filter(name='direction').exists(),
|
||||
'appartient_au_departement_informatique': est_equipe_info,
|
||||
'est_assistante_direction': est_assistante,
|
||||
'est_equipe_informatique': est_equipe_info,
|
||||
'liste_demande_reservation': list(liste_demande_reservation),
|
||||
}
|
||||
|
||||
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:
|
||||
print(reservation.statut)
|
||||
color = None
|
||||
if reservation.statut == "en_attente":
|
||||
color = "#ffc107"
|
||||
elif reservation.statut == "validee":
|
||||
color = "#198754"
|
||||
else:
|
||||
color = "#dc3545"
|
||||
|
||||
print(dict(Reservation.TYPE_CHOICES).get(reservation.salle))
|
||||
color = "#198754"
|
||||
|
||||
liste_reservation.append({
|
||||
"guid": reservation.pk,
|
||||
@@ -126,15 +152,45 @@ 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)
|
||||
|
||||
@login_required
|
||||
def liste_reservation_attente(request):
|
||||
"""Liste des réservations en attente, filtrée selon le profil :
|
||||
- Assistante de direction : voit les salles à valider (pas les liens choisis).
|
||||
- Équipe informatique : voit les liens choisis et les salles demandant zoom/ordi non traités.
|
||||
"""
|
||||
try:
|
||||
employe = Employe.objects.get(user=request.user)
|
||||
except Employe.DoesNotExist:
|
||||
return JsonResponse({"success": False, "message": "Profil employé introuvable"})
|
||||
|
||||
est_informatique = fonctions_utilitaire.est_equipe_informatique(employe)
|
||||
est_assistante = fonctions_utilitaire.est_assistante_direction(employe)
|
||||
|
||||
reservations = Reservation.objects.filter(statut="en_attente")
|
||||
|
||||
if est_assistante:
|
||||
# L'assistante voit les demandes de salle (formation / réunion)
|
||||
# Elle ne reçoit pas les demandes de simple lien (lien_zoom) qui vont à l'équipe info.
|
||||
reservations = reservations.exclude(salle="lien_zoom")
|
||||
elif est_informatique:
|
||||
# L'équipe informatique voit :
|
||||
# - les demandes de lien zoom directs
|
||||
# - les demandes de salle avec besoin_zoom ou besoin_ordi (déjà validés par l'assistance)
|
||||
# pour lesquelles le lien_zoom n'a pas encore était fourni (lien_zoom vide)
|
||||
reservations = reservations.filter(
|
||||
Q(salle="lien_zoom") | Q(besoin_zoom=True) | Q(besoin_ordi=True)
|
||||
)
|
||||
|
||||
print("Test :", [reservation.employe for reservation in reservations])
|
||||
|
||||
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),
|
||||
@@ -145,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
|
||||
@@ -161,55 +260,107 @@ 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)
|
||||
|
||||
@login_required
|
||||
def ajouter_lien_zoom(request:HttpRequest):
|
||||
"""Vue de gestion de l'ajout du lien zoom"""
|
||||
if request.method == 'POST':
|
||||
reservation_id= request.POST['id_reservation']
|
||||
try:
|
||||
reservation = Reservation.objects.get(id=reservation_id)
|
||||
except reservation.DoesNotExist:
|
||||
messages.error(request, "La resevertion selectionné n'existe pas.")
|
||||
return redirect("salle")
|
||||
|
||||
if not (reservation.besoin_zoom or reservation.salle == "lien_zoom"):
|
||||
messages.error(request, "Cette réservation ne nécessite pas de lien Zoom.")
|
||||
return redirect('gestion_salle:reservation-salle')
|
||||
elif reservation.statut in ["annulee", "refusee"]:
|
||||
messages.error(request, "Le lien Zoom ne peut être ajouté pour les réservations annulée ou refusée.")
|
||||
return redirect('gestion_salle:reservation-salle')
|
||||
"""Traitement de la réservation par l'équipe informatique :
|
||||
- Ajoute lien zoom si demandé.
|
||||
- Valide une réservation qui exigeait un traitement par l'équipe informatique.
|
||||
- Envoie un mail à toutes les équipes et au demandeur après validation.
|
||||
"""
|
||||
reservation = None
|
||||
|
||||
if request.method == "POST":
|
||||
lien = request.POST.get("lien_zoom")
|
||||
reservation.lien_zoom = lien
|
||||
reservation.statut = "validee"
|
||||
if request.method == 'POST':
|
||||
reservation_id = request.POST.get('id_reservation')
|
||||
try:
|
||||
reservation = Reservation.objects.get(id=reservation_id)
|
||||
except (Reservation.DoesNotExist, ValueError):
|
||||
messages.error(request, "La réservation sélectionnée n'existe pas.")
|
||||
return redirect('gestion_salle:reservation-salle')
|
||||
|
||||
try:
|
||||
employe = Employe.objects.get(user=request.user)
|
||||
except Employe.DoesNotExist:
|
||||
messages.error(request, "Profil employé introuvable.")
|
||||
return redirect('gestion_salle:reservation-salle')
|
||||
|
||||
# Vérification : seule l'équipe informatique peut traiter ces demandes
|
||||
if not fonctions_utilitaire.est_equipe_informatique(employe):
|
||||
messages.error(request, "Vous n'êtes pas autorisé à traiter cette demande.")
|
||||
return redirect('gestion_salle:reservation-salle')
|
||||
|
||||
if reservation.salle == "lien_zoom" or reservation.besoin_zoom:
|
||||
lien = request.POST.get("lien_zoom")
|
||||
if not lien:
|
||||
messages.error(request, "Veuillez fournir un lien Zoom.")
|
||||
return redirect('gestion_salle:reservation-salle')
|
||||
reservation.lien_zoom = lien
|
||||
|
||||
reservation.statut = "validee"
|
||||
reservation.save()
|
||||
messages.success(request, "Le lien Zoom a été ajouté et la réservation a été validée. ")
|
||||
messages.success(request, "La demande a été traitée et validée. ")
|
||||
|
||||
# Mail au demandeur
|
||||
fonctions_utilitaire.envoyer_mail(
|
||||
sujet="Réservation traitée",
|
||||
message=f"""Bonjour {reservation.employe.user.first_name} {reservation.employe.user.last_name}, votre demande de {dict(Reservation.TYPE_CHOICES).get(reservation.salle)}{' avec lien Zoom' if reservation.lien_zoom else ''} a été traitée. Veuillez vous connecter à la plateforme pour plus de détails.""",
|
||||
destinataires=[reservation.employe.user.email]
|
||||
)
|
||||
|
||||
# Mail aux emails de l'équipe informatique (hors utilisateur courant)
|
||||
autres_membres = Employe.objects.filter(departement__nom="Systeme informatique").exclude(id=employe.id).values_list('user__email', flat=True)
|
||||
if autres_membres:
|
||||
fonctions_utilitaire.envoyer_mail(
|
||||
sujet="Réservation validée par l'équipe informatique",
|
||||
message=f"""Bonjour,
|
||||
La réservation {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} du {reservation.date_debut.strftime('%d/%m/%Y')} pour {reservation.employe.user.first_name} {reservation.employe.user.last_name} a été traitée et validée par {employe.user.first_name} {employe.user.last_name}.\n\nVeuillez vous connecter à la plateforme pour plus de détails.""",
|
||||
destinataires=list(autres_membres)
|
||||
)
|
||||
|
||||
return redirect('gestion_salle:reservation-salle')
|
||||
|
||||
@login_required
|
||||
def annuler_reservation(request:HttpRequest):
|
||||
"""Vue de gestion de l'annulation de la reservation"""
|
||||
"""Vue de gestion de l'annulation de la reservation, réservée à l'employé demandeur ou aux validateurs."""
|
||||
if request.method == 'POST':
|
||||
reservation_id= request.POST['id_reservation']
|
||||
try:
|
||||
reservation = Reservation.objects.get(id=reservation_id)
|
||||
except Reservation.DoesNotExist:
|
||||
messages.error(request, "La resevertion selectionné n'existe pas.")
|
||||
return redirect("salle")
|
||||
|
||||
messages.error(request, "La reservation selectionné n'existe pas.")
|
||||
return redirect("gestion_salle:reservation-salle")
|
||||
|
||||
try:
|
||||
employe = Employe.objects.get(user=request.user)
|
||||
except Employe.DoesNotExist:
|
||||
messages.error(request, "Profil employé introuvable.")
|
||||
return redirect("gestion_salle:reservation-salle")
|
||||
|
||||
# Seul l'employé demandeur ou un validateur (assistante / équipe info) peut annuler
|
||||
est_demandeur = reservation.employe == employe
|
||||
est_validateur = fonctions_utilitaire.est_assistante_direction(employe) or fonctions_utilitaire.est_equipe_informatique(employe)
|
||||
|
||||
if not est_demandeur and not est_validateur:
|
||||
messages.error(request, "Vous n'êtes pas autorisé à annuler cette réservation.")
|
||||
return redirect('gestion_salle:reservation-salle')
|
||||
|
||||
reservation.statut = 'annulee'
|
||||
reservation.save()
|
||||
messages.success(request, "Votre réservation a été annulée.")
|
||||
messages.success(request, "La réservation a été annulée.")
|
||||
return redirect('gestion_salle:reservation-salle')
|
||||
|
||||
@login_required
|
||||
def valider_reservation(request: HttpRequest):
|
||||
"""Validation d'une réservation"""
|
||||
"""Validation d'une réservation selon le profil :
|
||||
- Assistante de direction : valide les demandes de salles (sauf lien_zoom) ;
|
||||
- Équipe informatique : valide les demandes de lien_zoom et les
|
||||
demandes de salle avec besoin zoom/ordi restées en attente après la première correction.
|
||||
"""
|
||||
if request.method == 'POST':
|
||||
reservation_id = request.POST.get('id_reservation')
|
||||
|
||||
@@ -217,37 +368,122 @@ def valider_reservation(request: HttpRequest):
|
||||
reservation = Reservation.objects.get(id=reservation_id)
|
||||
except Reservation.DoesNotExist:
|
||||
messages.error(request, "La réservation sélectionnée n'existe pas.")
|
||||
return redirect("salle")
|
||||
return redirect('gestion_salle:reservation-salle')
|
||||
|
||||
reservation.statut = 'validee'
|
||||
reservation.save()
|
||||
try:
|
||||
employe = Employe.objects.get(user=request.user)
|
||||
except Employe.DoesNotExist:
|
||||
messages.error(request, "Profil employé introuvable.")
|
||||
return redirect('gestion_salle:reservation-salle')
|
||||
|
||||
fonctions_utilitaire.envoyer_mail(
|
||||
sujet = "Reservation de salle",
|
||||
message = f"""Bonjour {reservation.employe.user.first_name} {reservation.employe.user.last_name}, votre reservation de la salle {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} du {reservation.date_debut.strftime('%d/%m/%Y')} au {reservation.date_fin.strftime('%d/%m/%Y')} pour motif "{reservation.motif_reservation}" a été validée. Veuillez vous connecter à la plateforme pour plus de détails.""",
|
||||
destinataires = [reservation.employe.user.email]
|
||||
)
|
||||
est_assistante = fonctions_utilitaire.est_assistante_direction(employe)
|
||||
est_equipe_info = fonctions_utilitaire.est_equipe_informatique(employe)
|
||||
|
||||
messages.success(request, f"La réservation de {request.user.first_name} {request.user.last_name} validée avec succès.")
|
||||
if est_assistante:
|
||||
# L'assistante ne doit pas traiter les simple lien_zoom
|
||||
if reservation.salle == "lien_zoom":
|
||||
messages.error(request, "Les demandes de lien Zoom sont traitées par l'équipe informatique.")
|
||||
return redirect('gestion_salle:reservation-salle')
|
||||
|
||||
if reservation.besoin_zoom or reservation.besoin_ordi:
|
||||
# La demande reste en attente, l'équipe informatique doit être notifiée
|
||||
# L'assistante a donné sa validation "salle", on garde le statut "en_attente"
|
||||
# pour que l'équipe informatique puisse traiter, et on envoie le mail à l'équipe.
|
||||
if fonctions_utilitaire.EMAIL_EQUIPE_INFORMATIQUE:
|
||||
fonctions_utilitaire.envoyer_mail(
|
||||
sujet="Réservation de salle à traiter par l'équipe informatique",
|
||||
message=f"""Bonjour,
|
||||
L'assistante de direction a validé la réservation de la salle {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} pour {reservation.employe.user.first_name} {reservation.employe.user.last_name} le {reservation.date_debut.strftime('%d/%m/%Y')} (de {reservation.heure_debut.strftime('%H:%M')} à {reservation.heure_fin.strftime('%H:%M')}).
|
||||
Cette demande nécessite {"un lien zoom" if reservation.besoin_zoom else ""}{" - un ordinateur" if reservation.besoin_ordi else ""}.
|
||||
|
||||
Veuillez vous connecter à la plateforme pour traiter cette demande.""",
|
||||
destinataires=fonctions_utilitaire.EMAIL_EQUIPE_INFORMATIQUE
|
||||
)
|
||||
|
||||
messages.success(request, "Réservation validée, une demande a été envoyée à l'équipe informatique.")
|
||||
reservation.statut = "en_attente" # reste en attente pour l'équipe info
|
||||
reservation.save()
|
||||
else:
|
||||
# Validation directe sans traitement informatique
|
||||
reservation.statut = "validee"
|
||||
reservation.save()
|
||||
fonctions_utilitaire.envoyer_mail(
|
||||
sujet="Réservation de salle validée",
|
||||
message=f"""Bonjour {reservation.employe.user.first_name} {reservation.employe.user.last_name}, votre réservation de la {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} du {reservation.date_debut.strftime('%d/%m/%Y')} au {reservation.date_fin.strftime('%d/%m/%Y')} pour motif "{reservation.motif_reservation}" a été validée. Veuillez vous connecter à la plateforme pour plus de détails.""",
|
||||
destinataires=[reservation.employe.user.email]
|
||||
)
|
||||
|
||||
elif est_equipe_info:
|
||||
# L'équipe informatique traite le reste : lien_zoom et besoins info
|
||||
if reservation.salle == "lien_zoom" or reservation.besoin_zoom or reservation.besoin_ordi:
|
||||
if reservation.besoin_zoom and not reservation.lien_zoom:
|
||||
# La demande exige un lien zoom qui n'a pas été encore rempli,
|
||||
# il est préférable de passer par ajouter_lien_zoom. On laisse traiter.
|
||||
messages.success(request, "Veuillez ajouter le lien Zoom via le champs dédié.")
|
||||
return redirect('gestion_salle:reservation-salle')
|
||||
|
||||
reservation.statut = "validee"
|
||||
reservation.save()
|
||||
|
||||
# Mail au demandeur + autres membres de l'équipe
|
||||
fonctions_utilitaire.envoyer_mail(
|
||||
sujet="Réservation validée",
|
||||
message=f"""Bonjour {reservation.employe.user.first_name} {reservation.employe.user.last_name}, votre demande de {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} a été traitée et validée. Veuillez vous connecter pour plus de détails.""",
|
||||
destinataires=[reservation.employe.user.email]
|
||||
)
|
||||
|
||||
autres = Employe.objects.filter(departement__nom="Systeme informatique").exclude(id=employe.id).values_list('user__email', flat=True)
|
||||
if autres:
|
||||
fonctions_utilitaire.envoyer_mail(
|
||||
sujet="Réservation traitée par un collègue",
|
||||
message=f"""Bonjour {request.user.first_name} {request.user.last_name}, la demande de {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} du {reservation.date_debut.strftime('%d/%m/%Y')} pour {reservation.employe.user.first_name} {reservation.employe.user.last_name} a été traitée et validée par {employe.user.first_name} {employe.user.last_name}. Veuillez vous connecter pour plus de détails.""",
|
||||
destinataires=list(autres)
|
||||
)
|
||||
else:
|
||||
messages.error(request, "Cette réservation ne relève pas de l'équipe informatique.")
|
||||
return redirect('gestion_salle:reservation-salle')
|
||||
else:
|
||||
messages.error(request, "Vous n'êtes pas autorisé à effectuer cette validation.")
|
||||
return redirect('gestion_salle:reservation-salle')
|
||||
|
||||
messages.success(request, f"La réservation de {request.user.first_name} {request.user.last_name} a été validée avec succès.")
|
||||
return redirect('gestion_salle:reservation-salle')
|
||||
|
||||
@login_required
|
||||
def refuser_reservation(request:HttpRequest):
|
||||
"""Vue de gestion de refus de la reservation"""
|
||||
"""Vue de gestion de refus de la reservation, autorisée à l'assistant de direction
|
||||
et l'équipe informatique selon le type de demande."""
|
||||
data = json.loads(request.body)
|
||||
reservation_id = data.get("id_reservation")
|
||||
try:
|
||||
reservation = Reservation.objects.get(id=reservation_id)
|
||||
except Reservation.DoesNotExist:
|
||||
return JsonResponse({"message": "La resevertion selectionné n'existe pas."})
|
||||
else:
|
||||
reservation.statut = "refusee"
|
||||
reservation.save()
|
||||
return JsonResponse({"message": "La reservation selectionné n'existe pas."})
|
||||
|
||||
fonctions_utilitaire.envoyer_mail(
|
||||
sujet = "Reservation de salle",
|
||||
message = f"""Bonjour {request.user.first_name} {request.user.last_name}, votre reservation de la salle {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} du {reservation.date_debut.strftime('%d/%m/%Y')} au {reservation.date_fin.strftime('%d/%m/%Y')} pour motif "{reservation.motif_reservation}" a été refusée. Veuillez vous connecter à l'Assistante de Direction pour plus de détails.""",
|
||||
destinataires = [reservation.employe.user.email]
|
||||
)
|
||||
try:
|
||||
employe = Employe.objects.get(user=request.user)
|
||||
except Employe.DoesNotExist:
|
||||
return JsonResponse({"message": "Profil employé introuvable."})
|
||||
|
||||
return JsonResponse({"message": "Réservation refusée avec succès."})
|
||||
est_assistante = fonctions_utilitaire.est_assistante_direction(employe)
|
||||
est_equipe_info = fonctions_utilitaire.est_equipe_informatique(employe)
|
||||
|
||||
peut_refuser = False
|
||||
if est_assistante and reservation.salle != "lien_zoom":
|
||||
peut_refuser = True
|
||||
elif est_equipe_info and (reservation.salle == "lien_zoom" or reservation.besoin_zoom or reservation.besoin_ordi):
|
||||
peut_refuser = True and (not reservation.besoin_zoom or reservation.lien_zoom)
|
||||
|
||||
if not peut_refuser:
|
||||
return JsonResponse({"message": "Vous n'êtes pas autorisé à refuser cette réservation."})
|
||||
|
||||
reservation.statut = "refusee"
|
||||
reservation.save()
|
||||
|
||||
fonctions_utilitaire.envoyer_mail(
|
||||
sujet = "Réservation refusée",
|
||||
message = f"""Bonjour {reservation.employe.user.first_name} {reservation.employe.user.last_name}, votre demande de réservation du {reservation.date_debut.strftime('%d/%m/%Y')} de {reservation.date_debut.strftime('%d/%m/%Y')} de {reservation.heure_debut.strftime('%H:%M')} à {reservation.heure_fin.strftime('%H:%M')} a été refusée.""",
|
||||
destinataires = [reservation.employe.user.email]
|
||||
)
|
||||
|
||||
return JsonResponse({"message": "Réservation refusée avec succès."})
|
||||
6
staticfiles/bootstrap.min.css
vendored
6
staticfiles/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -1,6 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class GestionCongeConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'gestion_conge'
|
||||
@@ -3,23 +3,6 @@ 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", {
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
// layout : "fitColumns",
|
||||
=======
|
||||
layout : "fitColumns",
|
||||
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
|
||||
=======
|
||||
layout : "fitColumns",
|
||||
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
|
||||
=======
|
||||
layout : "fitColumns",
|
||||
>>>>>>> 6026af5 (Bug : Affichages des conges)
|
||||
=======
|
||||
// layout : "fitColumns",
|
||||
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
|
||||
columns: [
|
||||
{"title": "Nom et Prénom", "field": "prenom_nom"},
|
||||
{"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{
|
||||
@@ -31,23 +14,7 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
|
||||
outputFormat:"dd/MM/yy",
|
||||
}},
|
||||
{"title": "Type de congé", "field": "type"},
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
{"title": "Date de la demande", "field": "date_demande", responsive: 1},
|
||||
=======
|
||||
{"title": "Date de la demande", "field": "date_demande"},
|
||||
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
|
||||
=======
|
||||
{"title": "Date de la demande", "field": "date_demande"},
|
||||
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
|
||||
=======
|
||||
{"title": "Date de la demande", "field": "date_demande"},
|
||||
>>>>>>> 6026af5 (Bug : Affichages des conges)
|
||||
=======
|
||||
{"title": "Date de la demande", "field": "date_demande", responsive: 1},
|
||||
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
|
||||
{"title": "Validation par supérieur hiérarchique", "field": "validation_hierarchique", formatter:"tickCross", formatterParams :{
|
||||
allowEmpty : true ,
|
||||
}},
|
||||
@@ -56,23 +23,7 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
|
||||
}},
|
||||
],
|
||||
pagination: true,
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
paginationSize: 5,
|
||||
=======
|
||||
paginationSize: 5
|
||||
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
|
||||
=======
|
||||
paginationSize: 5
|
||||
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
|
||||
=======
|
||||
paginationSize: 5
|
||||
>>>>>>> 6026af5 (Bug : Affichages des conges)
|
||||
=======
|
||||
paginationSize: 5,
|
||||
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
|
||||
})
|
||||
|
||||
const bouton_demande_conges = $("bouton-demande-conge");
|
||||
|
||||
@@ -9,30 +9,15 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
|
||||
{"title": "Projet", "field": "projet"},
|
||||
{"title": "Téléphone", "field": "telephone"},
|
||||
],
|
||||
// ajaxURL: url_liste_employe,
|
||||
pagination: true,
|
||||
paginationSize: 10,
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
responsiveLayout : "hide"
|
||||
=======
|
||||
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
|
||||
=======
|
||||
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
|
||||
=======
|
||||
>>>>>>> 6026af5 (Bug : Affichages des conges)
|
||||
=======
|
||||
responsiveLayout : "hide"
|
||||
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
|
||||
})
|
||||
|
||||
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);
|
||||
@@ -42,6 +27,7 @@ fetch(url_liste_employe)
|
||||
|
||||
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;
|
||||
@@ -224,23 +210,31 @@ $("enregistrerDetail").addEventListener("click", function() {
|
||||
const fonction = document.getElementById('detail-fonction').value;
|
||||
const dateEmbauche = document.getElementById('detail-dateEmbauche').value;
|
||||
const matricule = document.getElementById('detail-matricule').value;
|
||||
const departement = document.getElementById('detail-departement').value;
|
||||
|
||||
const url_enregistrer_detail = $("modalDetailEmployeBody").dataset.url;
|
||||
const url_enregistrer_detail = document.getElementById("modalDetailEmployeBody").dataset.url;
|
||||
|
||||
console.log("Le lien de l'enregistrement :", document.getElementById("modalDetailEmployeBody").dataset)
|
||||
|
||||
const payload = {
|
||||
"id": id_,
|
||||
"fonction": fonction,
|
||||
"date_embauche": dateEmbauche,
|
||||
"matricule": matricule,
|
||||
"departement": departement
|
||||
};
|
||||
|
||||
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
|
||||
})
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Réponse du serveur:', data);
|
||||
alert(data.message);
|
||||
location.reload();
|
||||
})
|
||||
@@ -274,4 +268,70 @@ const listeContratExpirant = new Tabulator("#listeContratExpirant", {
|
||||
},
|
||||
],
|
||||
ajaxURL: $("boutonContratExpirants").dataset.urlexpirants,
|
||||
})
|
||||
})
|
||||
|
||||
/* let tabContrats = null;
|
||||
|
||||
document.getElementById("modalContratsEmploye").addEventListener("show.bs.modal", function () {
|
||||
|
||||
const employeId = document.getElementById("detail-id").value;
|
||||
|
||||
if (!employeId) {
|
||||
alert("Aucun employé sélectionné");
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/employé/employe/${employeId}/contrats/`)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error("Erreur HTTP " + response.status);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
|
||||
document.getElementById("nom-employe-contrat").innerText =
|
||||
data.employe;
|
||||
|
||||
if (tabContrats) {
|
||||
tabContrats.destroy();
|
||||
}
|
||||
|
||||
tabContrats = new Tabulator(
|
||||
"#table-contrats-tabulator",
|
||||
{
|
||||
data: data.contrats,
|
||||
layout: "fitColumns",
|
||||
placeholder: "Aucun contrat",
|
||||
|
||||
columns: [
|
||||
{ title: "Numéro", field: "numero_contrat" },
|
||||
{ title: "Type", field: "type_contrat" },
|
||||
{ title: "Date début", field: "date_debut" },
|
||||
{ title: "Date fin", field: "date_fin" },
|
||||
{ title: "Salaire", field: "salaire_mensuel" },
|
||||
{ title: "Statut", field: "statut" },
|
||||
|
||||
{
|
||||
title: "Contrat",
|
||||
field: "fichier",
|
||||
formatter: function(cell) {
|
||||
const url = cell.getValue();
|
||||
|
||||
if (!url) {
|
||||
return "<span class='text-danger'>Aucun fichier</span>";
|
||||
}
|
||||
|
||||
return `<a href="${url}" target="_blank" class="btn btn-sm btn-primary">
|
||||
<i class="bi bi-file-earmark-pdf"></i> Ouvrir
|
||||
</a>`;
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Erreur :", error);
|
||||
});
|
||||
}); */
|
||||
|
||||
@@ -13,22 +13,20 @@ const tableau_certificat = new Tabulator("#tableau-certificat", {
|
||||
inputFormat:"yyyy-MM-dd",
|
||||
outputFormat:"dd/MM/yy",
|
||||
}},
|
||||
{"title": "Fichier", "field": "certificat", formatter:"link", formatterParams:{
|
||||
label:"Voir le fichier",
|
||||
target:"_blank",
|
||||
}},
|
||||
],
|
||||
ajaxURL: url_certificat,
|
||||
})
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
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);
|
||||
@@ -36,6 +34,10 @@ enregistrerProfil.addEventListener("click", (e) => {
|
||||
formData.append("adresse", $("adresse").value);
|
||||
formData.append("sexe", $("sexe").value);
|
||||
formData.append("date_naissance", $("date_naissance").value);
|
||||
const photoInput = $("photo");
|
||||
if (photoInput.files.length > 0) {
|
||||
formData.append("photo", photoInput.files[0]);
|
||||
}
|
||||
|
||||
fetch(url, {
|
||||
method: "POST",
|
||||
@@ -47,53 +49,4 @@ enregistrerProfil.addEventListener("click", (e) => {
|
||||
.then(response => response.json())
|
||||
.then(data => alert(data.message))
|
||||
.catch(error => console.error("Erreur:", error));
|
||||
});
|
||||
=======
|
||||
=======
|
||||
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
|
||||
=======
|
||||
>>>>>>> 6026af5 (Bug : Affichages des conges)
|
||||
|
||||
=======
|
||||
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
|
||||
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))
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
})
|
||||
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
|
||||
=======
|
||||
})
|
||||
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
|
||||
=======
|
||||
})
|
||||
>>>>>>> 6026af5 (Bug : Affichages des conges)
|
||||
=======
|
||||
.catch(error => console.error("Erreur:", error));
|
||||
});
|
||||
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
|
||||
});
|
||||
@@ -1,18 +1,5 @@
|
||||
const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur');
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
let table;
|
||||
=======
|
||||
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
|
||||
=======
|
||||
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
|
||||
=======
|
||||
>>>>>>> 6026af5 (Bug : Affichages des conges)
|
||||
=======
|
||||
let table;
|
||||
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
|
||||
|
||||
btnEnregistrerBailleur.addEventListener('click', function() {
|
||||
const form = document.getElementById('formBailleur');
|
||||
@@ -34,49 +21,27 @@ btnEnregistrerBailleur.addEventListener('click', function() {
|
||||
alert('Ce bailleur existe déjà dans la base de données.');
|
||||
}
|
||||
});
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
|
||||
});
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
|
||||
table = new Tabulator("#table-bailleurs", {
|
||||
ajaxURL: "/gestion-projet/bailleurs/",
|
||||
layout: "fitColumns",
|
||||
pagination: "local",
|
||||
paginationSize: 5,
|
||||
table = new Tabulator("#table-bailleurs", {
|
||||
ajaxURL: "/gestion-projet/bailleurs/",
|
||||
layout: "fitColumns",
|
||||
pagination: "local",
|
||||
paginationSize: 5,
|
||||
|
||||
columns: [
|
||||
{title: "#", formatter: "rownum", width: 60},
|
||||
{title: "Organisme", field: "nom_organisme"},
|
||||
{title: "Contact", field: "contact"},
|
||||
{title: "Email", field: "email"},
|
||||
{title: "Pays", field: "pays"},
|
||||
],
|
||||
rowDblClick: function(e, row) {
|
||||
const data = row.getData();
|
||||
columns: [
|
||||
{title: "#", formatter: "rownum", width: 60},
|
||||
{title: "Organisme", field: "nom_organisme"},
|
||||
{title: "Contact", field: "contact"},
|
||||
{title: "Email", field: "email"},
|
||||
{title: "Pays", field: "pays"},
|
||||
],
|
||||
rowDblClick: function(e, row) {
|
||||
const data = row.getData();
|
||||
|
||||
if (confirm(`Voulez-vous vraiment supprimer ${data.nom_organisme} ?`)) {
|
||||
supprimerBailleur(data.id);
|
||||
}
|
||||
if (confirm(`Voulez-vous vraiment supprimer ${data.nom_organisme} ?`)) {
|
||||
supprimerBailleur(data.id);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
});
|
||||
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
|
||||
=======
|
||||
});
|
||||
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
|
||||
=======
|
||||
});
|
||||
>>>>>>> 6026af5 (Bug : Affichages des conges)
|
||||
=======
|
||||
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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,
|
||||
@@ -39,7 +40,6 @@ tableau_liste_projet.on("rowClick", function (row, rowData) {
|
||||
$("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;
|
||||
@@ -61,4 +61,4 @@ tableau_liste_projet.on("rowClick", function (row, rowData) {
|
||||
// $('detail-projet-form').addEventListener('submit', (e) => {
|
||||
// e.preventDefault();
|
||||
// new FormData($("detail-projet-form"));
|
||||
// })
|
||||
// })
|
||||
|
||||
@@ -12,24 +12,14 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
|
||||
const modal = new bootstrap.Modal($("modalDetailReservation"));
|
||||
modal.show();
|
||||
fetch (`/gestion-salle/revervation/details/${event.guid}`)
|
||||
.then(response => response.json())
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Erreur HTTP ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
currentReservationId = data.id_reservation;
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
console.log(data);
|
||||
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
|
||||
=======
|
||||
console.log(data);
|
||||
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
|
||||
=======
|
||||
console.log(data);
|
||||
>>>>>>> 6026af5 (Bug : Affichages des conges)
|
||||
=======
|
||||
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
|
||||
$("id_reservation_detail").value = data.id_reservation;
|
||||
$("id_reservation_refus").value = data.id_reservation;
|
||||
$("id_reservation_zoom").value = data.id_reservation;
|
||||
@@ -42,40 +32,20 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
|
||||
$("motif_reservation").value=data.motif_reservation;
|
||||
$("besoin_zoom").checked=data.besoin_zoom;
|
||||
$("besoin_ordinateur").checked=data.besoin_ordinateur;
|
||||
$("lien_zoom").value=data.lien_zoom;
|
||||
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
$("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";
|
||||
}
|
||||
|
||||
=======
|
||||
=======
|
||||
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
|
||||
=======
|
||||
>>>>>>> 6026af5 (Bug : Affichages des conges)
|
||||
if(data.statut !== "annulee"){
|
||||
$("motif_refus_container").className = "d-none";
|
||||
}else{
|
||||
$("motif_refus").value=data.motif_refus;
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
|
||||
=======
|
||||
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
|
||||
=======
|
||||
>>>>>>> 6026af5 (Bug : Affichages des conges)
|
||||
=======
|
||||
if (data.besoin_zoom === false){
|
||||
$("lien_zoom_container").className = "d-none";
|
||||
}
|
||||
|
||||
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
|
||||
})
|
||||
.catch(error => console.error("Impossible de charger les détails de la réservation :", error));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -117,29 +87,13 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
})
|
||||
|
||||
$("bouton-annuler").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
|
||||
const url_annuler = $("formulaire-details").dataset.urlannuler;
|
||||
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
|
||||
console.log("URL d'annulation :");
|
||||
console.log($("formulaire-details"))
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
|
||||
=======
|
||||
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
|
||||
=======
|
||||
>>>>>>> 6026af5 (Bug : Affichages des conges)
|
||||
=======
|
||||
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
|
||||
fetch(
|
||||
url_annuler,
|
||||
{
|
||||
@@ -186,28 +140,15 @@ if($("ajoutZoom")){
|
||||
|
||||
if($("refuserReservation")){
|
||||
$("refuserReservation").addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
|
||||
const url = e.currentTarget.dataset.lienrefus;
|
||||
const idRes = $("id_reservation_detail").value;
|
||||
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
console.log(idRes);
|
||||
e.preventDefault();
|
||||
|
||||
=======
|
||||
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
|
||||
=======
|
||||
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
|
||||
=======
|
||||
>>>>>>> 6026af5 (Bug : Affichages des conges)
|
||||
=======
|
||||
console.log(idRes);
|
||||
e.preventDefault();
|
||||
|
||||
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
|
||||
fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -227,7 +168,7 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente"
|
||||
columns: [
|
||||
{title: "Employé", field: "employe"},
|
||||
{title: "Salle", field: "salle"},
|
||||
{title: "Date de l'evenement", field: "date_debut"},
|
||||
{title: "Date de l'evenement", field: "date_evenement"},
|
||||
{title: "Heure de début", field: "heure_debut"},
|
||||
{title: "Heure de fin", field: "heure_fin"},
|
||||
{title: "Motif de reservation", field: "motif_reservation"},
|
||||
@@ -239,52 +180,14 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente"
|
||||
tableau_reservation_attente.on("rowClick", (row, rowData) => {
|
||||
const data = rowData.getData();
|
||||
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
console.log(data);
|
||||
|
||||
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
|
||||
=======
|
||||
console.log(data);
|
||||
|
||||
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
|
||||
=======
|
||||
console.log(data);
|
||||
|
||||
>>>>>>> 6026af5 (Bug : Affichages des conges)
|
||||
=======
|
||||
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
|
||||
if(data.besoin_zoom === false){
|
||||
$("lien_zoom_container").className = 'd-none';
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
const id_user = $("current-user-id").dataset.userid;
|
||||
=======
|
||||
=======
|
||||
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
|
||||
=======
|
||||
>>>>>>> 6026af5 (Bug : Affichages des conges)
|
||||
if(data.statut !== "refusee"){
|
||||
$("motif_refus_container").className = 'd-none';
|
||||
}
|
||||
const id_user = $("current-user-id").dataset.userId;
|
||||
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
|
||||
=======
|
||||
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
|
||||
=======
|
||||
>>>>>>> 6026af5 (Bug : Affichages des conges)
|
||||
=======
|
||||
const id_user = $("current-user-id").dataset.userid;
|
||||
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
|
||||
$("id_reservation_detail").value = data.id;
|
||||
$("id_reservation_refus").value = data.id;
|
||||
$("id_reservation_zoom").value = data.id;
|
||||
@@ -292,53 +195,25 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
|
||||
$("employe").value=data.employe;
|
||||
$("salle").value=data.salle;
|
||||
$("statut-reservation").innerHTML=data.statut;
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
$("date_debut").value = data.date_debut;
|
||||
$("date_fin").value = data.date_fin;
|
||||
=======
|
||||
$("date_evenement").value=data.date_debut;
|
||||
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
|
||||
=======
|
||||
$("date_evenement").value=data.date_debut;
|
||||
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
|
||||
=======
|
||||
$("date_evenement").value=data.date_debut;
|
||||
>>>>>>> 6026af5 (Bug : Affichages des conges)
|
||||
=======
|
||||
$("date_debut").value = data.date_debut;
|
||||
$("date_fin").value = data.date_fin;
|
||||
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
|
||||
$("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;
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
|
||||
|
||||
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";
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
$("motif_refus").value=data.motif_refus;
|
||||
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
|
||||
=======
|
||||
$("motif_refus").value=data.motif_refus;
|
||||
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
|
||||
=======
|
||||
$("motif_refus").value=data.motif_refus;
|
||||
>>>>>>> 6026af5 (Bug : Affichages des conges)
|
||||
=======
|
||||
>>>>>>> 3b587d2a1a4376551b4b694a38e0e24a1c4cf4c5
|
||||
|
||||
const modal = new bootstrap.Modal($("modalDetailReservation"));
|
||||
bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,278 +0,0 @@
|
||||
/* === Print === */
|
||||
@media print {
|
||||
nav, .btn, .sidebar, .form-select, .form-control {
|
||||
display: none !important;
|
||||
}
|
||||
body {
|
||||
font-size: 12pt;
|
||||
color: black;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 768px) {
|
||||
main {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* === Body & Container === */
|
||||
body {
|
||||
background-color: #e4dccc;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
.dashboard-container {
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.05);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 30px auto;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
width: 220px;
|
||||
}
|
||||
main {
|
||||
margin-left: 220px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* === Sidebar === */
|
||||
.sidebar {
|
||||
background: linear-gradient(180deg, #b35400, #993d00); /* dégradé orange foncé */
|
||||
color: white;
|
||||
min-height: 100vh; /* prend toute la hauteur */
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
overflow-y: auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sidebar .nav-link:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 5px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.sidebar .dropdown-menu {
|
||||
display: none;
|
||||
position: relative;
|
||||
left: 10px;
|
||||
top: -5px;
|
||||
border: none;
|
||||
padding-left: 1rem;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.sidebar .dropdown:hover .dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.sidebar a:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* === Navbar / Logo / Profil === */
|
||||
.logo {
|
||||
max-width: 80px;
|
||||
}
|
||||
|
||||
.img-profil {
|
||||
width: 80px !important;
|
||||
height: 80px !important;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* === Cards === */
|
||||
.card {
|
||||
border: 1px solid #dee2e6;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
border-radius: 8px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: bold !important;
|
||||
color: black !important;
|
||||
font-size: 1rem;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.card-body h5 {
|
||||
color: black !important;
|
||||
font-weight: bold;
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
/* === Stat cards === */
|
||||
.stat-card {
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(113, 11, 11, 0.1);
|
||||
}
|
||||
|
||||
/* === Employé / Projet details === */
|
||||
.employe-detail {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.employe-detail .card-title {
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.employe-detail p {
|
||||
margin-bottom: 0.4rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.employe-detail a.btn {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.employe-card, .projet-card {
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
padding: 2.5rem;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* === Forms === */
|
||||
.form-wrapper {
|
||||
max-width: 1000px;
|
||||
margin: auto;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
|
||||
form label {
|
||||
font-weight: 600;
|
||||
margin-top: 1rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
form input, form select, form textarea {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
margin-top: 0.3rem;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
/* === Formation badges === */
|
||||
.formation-badge {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.formation-actions {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.formation-badge:hover .formation-actions {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.formation-actions button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #fff;
|
||||
margin-left: 5px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* === Context menu === */
|
||||
#contextMenu {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
/* === Transitions / Animations === */
|
||||
.fade-out {
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
#main-content {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
|
||||
.login-box {
|
||||
background: linear-gradient(to right, #f5f7fa, #c3cfe2);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.3);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
/* === Responsive === */
|
||||
@media (max-width: 576px) {
|
||||
.modal-dialog {
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-footer button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.img-profil {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
font-size: 0.9rem;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user