Début du projet

This commit is contained in:
LAINEY Grégory committed 2026-09-25 11:02:14 +02:00
commit 43b1f166d7
28 files changed
+4284

No files matched your search

+119
View File
@@ -0,0 +1,119 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fiche animal — Mon Poulailler</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<header class="entete">
<h1><a href="/">Mon Poulailler</a></h1>
<nav>
<a href="/">Animaux</a>
<a href="/archives.html">Archives</a>
<a href="/comptes.html" id="lienComptes" hidden>Comptes</a>
<button type="button" id="boutonDeconnexion">Déconnexion</button>
</nav>
<p class="info-session">Connecté : <span id="nomUtilisateur"></span></p>
</header>
<main>
<p><a href="/" class="lien-retour">← Retour à la liste</a></p>
<section id="ficheAnimal" class="fiche">
<p>Chargement…</p>
</section>
<section id="sectionPeriodes" hidden>
<h2>Périodes de ponte</h2>
<div id="listePonte"></div>
<button type="button" id="boutonAjouterPonte">Ajouter une période de ponte</button>
<h2>Périodes de couvage</h2>
<div id="listeCouvage"></div>
<button type="button" id="boutonAjouterCouvage">Ajouter une période de couvage</button>
</section>
</main>
<dialog id="dialoguePeriode">
<form id="formPeriode" method="dialog">
<h3 id="titrePeriode">Nouvelle période</h3>
<input type="hidden" id="typePeriode">
<label for="dateDebut">Date de début</label>
<input type="date" id="dateDebut" required>
<label for="dateFin">Date de fin (vide si en cours)</label>
<input type="date" id="dateFin">
<label for="remarque">Remarque</label>
<textarea id="remarque" rows="3"></textarea>
<p id="erreurPeriode" class="erreur" hidden></p>
<div class="actions-dialogue">
<button type="button" id="annulerPeriode" class="secondaire">Annuler</button>
<button type="submit">Enregistrer</button>
</div>
</form>
</dialog>
<dialog id="dialogueDeces">
<form id="formDeces" method="dialog">
<h3>Marquer comme décédé</h3>
<p>L'animal passera dans les archives. Les périodes en cours seront clôturées.</p>
<label for="dateFinDeVie">Date de fin de vie</label>
<input type="date" id="dateFinDeVie" required>
<div class="actions-dialogue">
<button type="button" id="annulerDeces" class="secondaire">Annuler</button>
<button type="submit" class="danger">Confirmer</button>
</div>
</form>
</dialog>
<dialog id="dialogueAnimal">
<form id="formAnimal" method="dialog">
<h3>Modifier l'animal</h3>
<input type="hidden" id="animalId">
<label for="type">Type</label>
<select id="type" required>
<option value="poule">Poule</option>
<option value="coq">Coq</option>
</select>
<label for="couleurBague">Couleur de bague</label>
<input type="text" id="couleurBague">
<label for="numeroBague">Numéro de bague</label>
<input type="text" id="numeroBague">
<label for="dateNaissance">Date de naissance / arrivée</label>
<input type="date" id="dateNaissance">
<label for="etatSante">État de santé</label>
<select id="etatSante">
<option value="bon">Bon</option>
<option value="moyen">Moyen</option>
<option value="mauvais">Mauvais</option>
</select>
<label for="photo">Nouvelle photo (optionnel)</label>
<input type="file" id="photo" accept="image/*">
<p id="erreurAnimal" class="erreur" hidden></p>
<div class="actions-dialogue">
<button type="button" id="annulerAnimal" class="secondaire">Annuler</button>
<button type="submit">Enregistrer</button>
</div>
</form>
</dialog>
<script src="/js/commun.js"></script>
<script src="/js/fiche.js"></script>
</body>
</html>
+32
View File
@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Archives — Mon Poulailler</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<header class="entete">
<h1><a href="/">Mon Poulailler</a></h1>
<nav>
<a href="/">Animaux</a>
<a href="/archives.html" class="actif">Archives</a>
<a href="/comptes.html" id="lienComptes" hidden>Comptes</a>
<button type="button" id="boutonDeconnexion">Déconnexion</button>
</nav>
<p class="info-session">Connecté : <span id="nomUtilisateur"></span></p>
</header>
<main>
<section>
<h2>Animaux décédés</h2>
<div id="listeArchives" class="grille-animaux"></div>
<p id="messageVide" class="message-vide" hidden>Aucun animal archivé.</p>
</section>
</main>
<script src="/js/commun.js"></script>
<script src="/js/archives.js"></script>
</body>
</html>
+68
View File
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Comptes — Mon Poulailler</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<header class="entete">
<h1><a href="/">Mon Poulailler</a></h1>
<nav>
<a href="/">Animaux</a>
<a href="/archives.html">Archives</a>
<a href="/comptes.html" class="actif" id="lienComptes">Comptes</a>
<button type="button" id="boutonDeconnexion">Déconnexion</button>
</nav>
<p class="info-session">Connecté : <span id="nomUtilisateur"></span></p>
</header>
<main>
<section>
<div class="titre-avec-action">
<h2>Comptes utilisateurs</h2>
<button type="button" id="boutonNouveauCompte">Créer un compte</button>
</div>
<table class="tableau">
<thead>
<tr>
<th>Login</th>
<th>Profil</th>
</tr>
</thead>
<tbody id="listeComptes"></tbody>
</table>
</section>
</main>
<dialog id="dialogueCompte">
<form id="formCompte" method="dialog">
<h3>Créer un compte</h3>
<label for="login">Identifiant</label>
<input type="text" id="login" required>
<label for="motDePasse">Mot de passe</label>
<input type="password" id="motDePasse" required minlength="4">
<label for="profil">Profil</label>
<select id="profil">
<option value="utilisateur">Utilisateur</option>
<option value="admin">Admin</option>
</select>
<p id="erreurCompte" class="erreur" hidden></p>
<div class="actions-dialogue">
<button type="button" id="annulerCompte" class="secondaire">Annuler</button>
<button type="submit">Créer</button>
</div>
</form>
</dialog>
<script src="/js/commun.js"></script>
<script src="/js/comptes.js"></script>
</body>
</html>
+30
View File
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Connexion — Mon Poulailler</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body class="page-connexion">
<main class="boite-connexion">
<h1>Mon Poulailler</h1>
<p class="sous-titre">Connexion</p>
<form id="formConnexion">
<label for="login">Identifiant</label>
<input type="text" id="login" name="login" required autocomplete="username">
<label for="motDePasse">Mot de passe</label>
<input type="password" id="motDePasse" name="motDePasse" required autocomplete="current-password">
<p id="messageErreur" class="erreur" hidden></p>
<button type="submit">Se connecter</button>
</form>
</main>
<script src="/js/commun.js"></script>
<script src="/js/connexion.js"></script>
</body>
</html>
+476
View File
@@ -0,0 +1,476 @@
:root
{
--vert: #3d6b4f;
--vert-fonce: #2a4a37;
--beige: #f7f3eb;
--creme: #fffdf8;
--texte: #2c2c2c;
--bordure: #d4cbb8;
--erreur: #a33;
--danger: #8b3a3a;
}
*
{
box-sizing: border-box;
}
body
{
margin: 0;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
background: var(--beige);
color: var(--texte);
line-height: 1.5;
}
a
{
color: var(--vert);
}
/* ----- Connexion ----- */
.page-connexion
{
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background:
linear-gradient(160deg, #e8f0e4 0%, var(--beige) 50%, #efe6d4 100%);
}
.boite-connexion
{
background: var(--creme);
padding: 2rem;
border: 1px solid var(--bordure);
width: 100%;
max-width: 360px;
}
.boite-connexion h1
{
margin: 0 0 0.25rem;
color: var(--vert-fonce);
text-align: center;
}
.sous-titre
{
text-align: center;
margin-top: 0;
color: #666;
}
/* ----- En-tête ----- */
.entete
{
background: var(--vert);
color: white;
padding: 1rem 1.5rem;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 1rem;
}
.entete h1
{
margin: 0;
font-size: 1.4rem;
}
.entete h1 a
{
color: white;
text-decoration: none;
}
.entete nav
{
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
flex: 1;
}
.entete nav a
{
color: #e8f0e4;
text-decoration: none;
padding: 0.25rem 0.5rem;
}
.entete nav a.actif,
.entete nav a:hover
{
text-decoration: underline;
}
.info-session
{
margin: 0;
font-size: 0.9rem;
opacity: 0.9;
}
main
{
max-width: 960px;
margin: 0 auto;
padding: 1.5rem;
}
/* ----- Formulaires ----- */
label
{
display: block;
margin-top: 0.75rem;
margin-bottom: 0.25rem;
font-weight: 600;
}
input,
select,
textarea,
button
{
font: inherit;
}
input[type="text"],
input[type="password"],
input[type="date"],
input[type="file"],
select,
textarea
{
width: 100%;
padding: 0.5rem;
border: 1px solid var(--bordure);
background: white;
}
button
{
margin-top: 1rem;
padding: 0.55rem 1rem;
background: var(--vert);
color: white;
border: none;
cursor: pointer;
}
button:hover
{
background: var(--vert-fonce);
}
button.secondaire
{
background: #888;
}
button.secondaire:hover
{
background: #666;
}
button.danger
{
background: var(--danger);
}
.erreur
{
color: var(--erreur);
margin-top: 0.75rem;
}
/* ----- Stats ----- */
.statistiques
{
background: var(--creme);
border: 1px solid var(--bordure);
padding: 1rem 1.25rem;
margin-bottom: 1.5rem;
}
.statistiques h2
{
margin-top: 0;
font-size: 1.1rem;
}
.liste-stats
{
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.liste-stats li
{
background: #eef5ee;
padding: 0.5rem 0.85rem;
}
.liste-stats span
{
font-weight: 700;
color: var(--vert-fonce);
}
/* ----- Liste animaux ----- */
.titre-avec-action
{
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
}
.titre-avec-action h2
{
margin: 0;
}
.titre-avec-action button
{
margin-top: 0;
}
.grille-animaux
{
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.carte-animal
{
background: var(--creme);
border: 1px solid var(--bordure);
padding: 0.75rem;
text-decoration: none;
color: inherit;
display: block;
}
.carte-animal:hover
{
border-color: var(--vert);
}
.carte-animal img
{
width: 100%;
height: 140px;
object-fit: cover;
background: #ddd;
display: block;
}
.carte-animal .sans-photo
{
width: 100%;
height: 140px;
background: #ddd;
display: flex;
align-items: center;
justify-content: center;
color: #666;
font-size: 0.9rem;
}
.carte-animal h3
{
margin: 0.5rem 0 0.25rem;
font-size: 1rem;
text-transform: capitalize;
}
.carte-animal p
{
margin: 0.15rem 0;
font-size: 0.9rem;
}
.badge
{
display: inline-block;
font-size: 0.75rem;
padding: 0.1rem 0.4rem;
background: #eef5ee;
margin-right: 0.25rem;
}
.message-vide
{
color: #666;
font-style: italic;
}
/* ----- Fiche ----- */
.fiche
{
background: var(--creme);
border: 1px solid var(--bordure);
padding: 1.25rem;
display: grid;
grid-template-columns: 200px 1fr;
gap: 1.25rem;
}
.fiche img,
.fiche .sans-photo
{
width: 200px;
height: 200px;
object-fit: cover;
background: #ddd;
}
.fiche .sans-photo
{
display: flex;
align-items: center;
justify-content: center;
color: #666;
}
.fiche dl
{
margin: 0;
}
.fiche dt
{
font-weight: 600;
margin-top: 0.5rem;
}
.fiche dd
{
margin: 0.15rem 0 0;
}
.actions-fiche
{
margin-top: 1rem;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.actions-fiche button
{
margin-top: 0;
}
.lien-retour
{
text-decoration: none;
}
#sectionPeriodes h2
{
margin-top: 2rem;
}
.tableau-periodes
{
width: 100%;
border-collapse: collapse;
margin: 0.75rem 0;
background: var(--creme);
}
.tableau-periodes th,
.tableau-periodes td
{
border: 1px solid var(--bordure);
padding: 0.5rem;
text-align: left;
}
.tableau
{
width: 100%;
border-collapse: collapse;
margin-top: 1rem;
background: var(--creme);
}
.tableau th,
.tableau td
{
border: 1px solid var(--bordure);
padding: 0.6rem;
text-align: left;
}
/* ----- Dialogues ----- */
dialog
{
border: 1px solid var(--bordure);
padding: 1.25rem;
max-width: 420px;
width: 90%;
background: var(--creme);
}
dialog::backdrop
{
background: rgba(0, 0, 0, 0.4);
}
.actions-dialogue
{
display: flex;
justify-content: flex-end;
gap: 0.5rem;
margin-top: 0.5rem;
}
.actions-dialogue button
{
margin-top: 0.5rem;
}
/* ----- Responsive ----- */
@media (max-width: 640px)
{
.fiche
{
grid-template-columns: 1fr;
}
.fiche img,
.fiche .sans-photo
{
width: 100%;
height: 200px;
}
.entete
{
flex-direction: column;
align-items: flex-start;
}
}
+87
View File
@@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mon Poulailler</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<header class="entete">
<h1><a href="/">Mon Poulailler</a></h1>
<nav>
<a href="/" class="actif">Animaux</a>
<a href="/archives.html">Archives</a>
<a href="/comptes.html" id="lienComptes" hidden>Comptes</a>
<button type="button" id="boutonDeconnexion">Déconnexion</button>
</nav>
<p class="info-session">Connecté : <span id="nomUtilisateur"></span></p>
</header>
<main>
<section class="statistiques" id="blocStatistiques">
<h2>Statistiques</h2>
<ul class="liste-stats">
<li><span id="statPoules">0</span> poules</li>
<li><span id="statCoqs">0</span> coqs</li>
<li><span id="statTotal">0</span> actifs</li>
<li><span id="statPonte">0</span> en ponte</li>
<li><span id="statCouvage">0</span> en couvage</li>
</ul>
</section>
<section>
<div class="titre-avec-action">
<h2>Animaux actifs</h2>
<button type="button" id="boutonNouvelAnimal">Ajouter un animal</button>
</div>
<div id="listeAnimaux" class="grille-animaux"></div>
<p id="messageVide" class="message-vide" hidden>Aucun animal actif pour le moment.</p>
</section>
</main>
<!-- Formulaire d'ajout / modification -->
<dialog id="dialogueAnimal">
<form id="formAnimal" method="dialog">
<h3 id="titreDialogueAnimal">Ajouter un animal</h3>
<input type="hidden" id="animalId">
<label for="type">Type</label>
<select id="type" required>
<option value="poule">Poule</option>
<option value="coq">Coq</option>
</select>
<label for="couleurBague">Couleur de bague</label>
<input type="text" id="couleurBague" placeholder="ex. rouge">
<label for="numeroBague">Numéro de bague</label>
<input type="text" id="numeroBague" placeholder="ex. 12">
<label for="dateNaissance">Date de naissance / arrivée</label>
<input type="date" id="dateNaissance">
<label for="etatSante">État de santé</label>
<select id="etatSante">
<option value="bon">Bon</option>
<option value="moyen">Moyen</option>
<option value="mauvais">Mauvais</option>
</select>
<label for="photo">Photo</label>
<input type="file" id="photo" accept="image/*">
<p id="erreurAnimal" class="erreur" hidden></p>
<div class="actions-dialogue">
<button type="button" id="annulerAnimal" class="secondaire">Annuler</button>
<button type="submit">Enregistrer</button>
</div>
</form>
</dialog>
<script src="/js/commun.js"></script>
<script src="/js/accueil.js"></script>
</body>
</html>
+114
View File
@@ -0,0 +1,114 @@
/**
* Page d'accueil : statistiques + liste des animaux actifs.
*/
let utilisateurCourant = null;
async function chargerStatistiques()
{
const stats = await appelerApi("/api/statistiques");
if (!stats)
{
return;
}
document.getElementById("statPoules").textContent = stats.nombrePoulesActives;
document.getElementById("statCoqs").textContent = stats.nombreCoqsActifs;
document.getElementById("statTotal").textContent = stats.nombreTotalActifs;
document.getElementById("statPonte").textContent = stats.nombreEnPonte;
document.getElementById("statCouvage").textContent = stats.nombreEnCouvage;
}
async function chargerAnimaux()
{
const animaux = await appelerApi("/api/animaux?actifs=oui");
if (!animaux)
{
return;
}
const liste = document.getElementById("listeAnimaux");
const messageVide = document.getElementById("messageVide");
if (animaux.length === 0)
{
liste.innerHTML = "";
messageVide.hidden = false;
return;
}
messageVide.hidden = true;
liste.innerHTML = animaux.map(htmlCarteAnimal).join("");
}
function ouvrirDialogueAjout()
{
document.getElementById("titreDialogueAnimal").textContent = "Ajouter un animal";
document.getElementById("animalId").value = "";
document.getElementById("formAnimal").reset();
document.getElementById("erreurAnimal").hidden = true;
document.getElementById("dialogueAnimal").showModal();
}
document.getElementById("boutonNouvelAnimal").addEventListener("click", ouvrirDialogueAjout);
document.getElementById("annulerAnimal").addEventListener("click", function ()
{
document.getElementById("dialogueAnimal").close();
});
document.getElementById("formAnimal").addEventListener("submit", async function (event)
{
event.preventDefault();
const erreurAnimal = document.getElementById("erreurAnimal");
erreurAnimal.hidden = true;
const formData = new FormData();
formData.append("type", document.getElementById("type").value);
formData.append("couleurBague", document.getElementById("couleurBague").value);
formData.append("numeroBague", document.getElementById("numeroBague").value);
formData.append("dateNaissance", document.getElementById("dateNaissance").value);
formData.append("etatSante", document.getElementById("etatSante").value);
const photo = document.getElementById("photo").files[0];
if (photo)
{
formData.append("photo", photo);
}
try
{
await appelerApi("/api/animaux", {
method: "POST",
body: formData
});
document.getElementById("dialogueAnimal").close();
await chargerAnimaux();
await chargerStatistiques();
}
catch (erreur)
{
erreurAnimal.textContent = erreur.message;
erreurAnimal.hidden = false;
}
});
async function demarrer()
{
utilisateurCourant = await verifierSession();
if (!utilisateurCourant)
{
return;
}
brancherDeconnexion();
await chargerStatistiques();
await chargerAnimaux();
}
demarrer();
+41
View File
@@ -0,0 +1,41 @@
/**
* Page archives : animaux décédés.
*/
async function chargerArchives()
{
const animaux = await appelerApi("/api/animaux?archives=oui");
if (!animaux)
{
return;
}
const liste = document.getElementById("listeArchives");
const messageVide = document.getElementById("messageVide");
if (animaux.length === 0)
{
liste.innerHTML = "";
messageVide.hidden = false;
return;
}
messageVide.hidden = true;
liste.innerHTML = animaux.map(htmlCarteAnimal).join("");
}
async function demarrer()
{
const utilisateur = await verifierSession();
if (!utilisateur)
{
return;
}
brancherDeconnexion();
await chargerArchives();
}
demarrer();
+133
View File
@@ -0,0 +1,133 @@
/**
* Fonctions partagées entre les pages.
*/
/**
* Appelle l'API et retourne la réponse JSON.
* Redirige vers la page de connexion si non authentifié (401).
*/
async function appelerApi(url, options)
{
const reponse = await fetch(url, options);
const donnees = await reponse.json().catch(function ()
{
return {};
});
if (reponse.status === 401 && !url.includes("/api/connexion"))
{
window.location.href = "/connexion.html";
return null;
}
if (!reponse.ok)
{
throw new Error(donnees.erreur || "Erreur serveur");
}
return donnees;
}
/**
* Vérifie la session. Redirige vers connexion si besoin.
* Affiche le nom et le lien Comptes pour l'admin.
*/
async function verifierSession()
{
const session = await appelerApi("/api/session");
if (!session || !session.connecte)
{
window.location.href = "/connexion.html";
return null;
}
const nomUtilisateur = document.getElementById("nomUtilisateur");
if (nomUtilisateur)
{
nomUtilisateur.textContent = session.utilisateur.login + " (" + session.utilisateur.profil + ")";
}
const lienComptes = document.getElementById("lienComptes");
if (lienComptes && session.utilisateur.profil === "admin")
{
lienComptes.hidden = false;
}
return session.utilisateur;
}
/**
* Branche le bouton de déconnexion s'il est présent sur la page.
*/
function brancherDeconnexion()
{
const bouton = document.getElementById("boutonDeconnexion");
if (!bouton)
{
return;
}
bouton.addEventListener("click", async function ()
{
await appelerApi("/api/deconnexion", { method: "POST" });
window.location.href = "/connexion.html";
});
}
/**
* Affiche une étiquette lisible pour le type d'animal.
*/
function libelleType(type)
{
return type === "coq" ? "Coq" : "Poule";
}
/**
* Affiche une étiquette pour l'état de santé.
*/
function libelleSante(etat)
{
if (etat === "moyen")
{
return "Moyen";
}
if (etat === "mauvais")
{
return "Mauvais";
}
return "Bon";
}
/**
* Construit le HTML d'une carte animal (liste).
*/
function htmlCarteAnimal(animal)
{
let photoHtml = "<div class=\"sans-photo\">Pas de photo</div>";
if (animal.photo)
{
photoHtml = "<img src=\"" + animal.photo + "\" alt=\"Photo de l'animal\">";
}
let badges = "";
if (animal.actif === false && animal.dateFinDeVie)
{
badges += "<span class=\"badge\">Décédé le " + animal.dateFinDeVie + "</span>";
}
return (
"<a class=\"carte-animal\" href=\"/animal.html?id=" + animal.id + "\">" +
photoHtml +
"<h3>" + libelleType(animal.type) + "</h3>" +
"<p>Bague : " + (animal.couleurBague || "—") + " " + (animal.numeroBague || "") + "</p>" +
"<p>Santé : " + libelleSante(animal.etatSante) + "</p>" +
badges +
"</a>"
);
}
+86
View File
@@ -0,0 +1,86 @@
/**
* Page comptes (admin uniquement) : liste et création.
*/
async function chargerComptes()
{
const comptes = await appelerApi("/api/utilisateurs");
if (!comptes)
{
return;
}
const tbody = document.getElementById("listeComptes");
tbody.innerHTML = comptes.map(function (compte)
{
return (
"<tr>" +
"<td>" + compte.login + "</td>" +
"<td>" + compte.profil + "</td>" +
"</tr>"
);
}).join("");
}
document.getElementById("boutonNouveauCompte").addEventListener("click", function ()
{
document.getElementById("formCompte").reset();
document.getElementById("erreurCompte").hidden = true;
document.getElementById("dialogueCompte").showModal();
});
document.getElementById("annulerCompte").addEventListener("click", function ()
{
document.getElementById("dialogueCompte").close();
});
document.getElementById("formCompte").addEventListener("submit", async function (event)
{
event.preventDefault();
const erreurCompte = document.getElementById("erreurCompte");
erreurCompte.hidden = true;
try
{
await appelerApi("/api/utilisateurs", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
login: document.getElementById("login").value.trim(),
motDePasse: document.getElementById("motDePasse").value,
profil: document.getElementById("profil").value
})
});
document.getElementById("dialogueCompte").close();
await chargerComptes();
}
catch (erreur)
{
erreurCompte.textContent = erreur.message;
erreurCompte.hidden = false;
}
});
async function demarrer()
{
const utilisateur = await verifierSession();
if (!utilisateur)
{
return;
}
if (utilisateur.profil !== "admin")
{
window.location.href = "/";
return;
}
brancherDeconnexion();
await chargerComptes();
}
demarrer();
+42
View File
@@ -0,0 +1,42 @@
/**
* Page de connexion.
*/
document.getElementById("formConnexion").addEventListener("submit", async function (event)
{
event.preventDefault();
const messageErreur = document.getElementById("messageErreur");
messageErreur.hidden = true;
const login = document.getElementById("login").value.trim();
const motDePasse = document.getElementById("motDePasse").value;
try
{
await appelerApi("/api/connexion", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
login: login,
motDePasse: motDePasse
})
});
window.location.href = "/";
}
catch (erreur)
{
messageErreur.textContent = erreur.message;
messageErreur.hidden = false;
}
});
/* Si déjà connecté, aller directement à l'accueil */
appelerApi("/api/session").then(function (session)
{
if (session && session.connecte)
{
window.location.href = "/";
}
});
+319
View File
@@ -0,0 +1,319 @@
/**
* Page fiche animal : détail, modification, périodes, décès.
*/
let animalCourant = null;
let periodesPonte = [];
let periodesCouvage = [];
function recupererIdDepuisUrl()
{
const parametres = new URLSearchParams(window.location.search);
return parametres.get("id");
}
function afficherFiche()
{
const animal = animalCourant;
let photoHtml = "<div class=\"sans-photo\">Pas de photo</div>";
if (animal.photo)
{
photoHtml = "<img src=\"" + animal.photo + "\" alt=\"Photo\">";
}
let actions = "";
if (animal.actif)
{
actions =
"<div class=\"actions-fiche\">" +
"<button type=\"button\" id=\"boutonModifier\">Modifier</button>" +
"<button type=\"button\" id=\"boutonDeces\" class=\"danger\">Marquer comme décédé</button>" +
"</div>";
}
const dateFin = animal.dateFinDeVie
? "<dt>Date de fin de vie</dt><dd>" + animal.dateFinDeVie + "</dd>"
: "";
document.getElementById("ficheAnimal").innerHTML =
photoHtml +
"<div>" +
"<h2>" + libelleType(animal.type) + "</h2>" +
"<dl>" +
"<dt>Couleur de bague</dt><dd>" + (animal.couleurBague || "—") + "</dd>" +
"<dt>Numéro de bague</dt><dd>" + (animal.numeroBague || "—") + "</dd>" +
"<dt>Date de naissance</dt><dd>" + (animal.dateNaissance || "—") + "</dd>" +
"<dt>État de santé</dt><dd>" + libelleSante(animal.etatSante) + "</dd>" +
"<dt>Statut</dt><dd>" + (animal.actif ? "Actif" : "Archivé") + "</dd>" +
dateFin +
"</dl>" +
actions +
"</div>";
if (animal.actif)
{
document.getElementById("boutonModifier").addEventListener("click", ouvrirModification);
document.getElementById("boutonDeces").addEventListener("click", ouvrirDeces);
}
/* Ponte et couvage : poules uniquement */
const sectionPeriodes = document.getElementById("sectionPeriodes");
if (animal.type === "poule")
{
sectionPeriodes.hidden = false;
afficherPeriodes();
}
else
{
sectionPeriodes.hidden = true;
}
}
function htmlTableauPeriodes(periodes, typePeriode)
{
if (periodes.length === 0)
{
return "<p class=\"message-vide\">Aucune période enregistrée.</p>";
}
let lignes = periodes.map(function (periode)
{
const enCours = !periode.dateFin;
const statut = enCours ? "<span class=\"badge\">En cours</span>" : "";
let boutonCloture = "";
if (enCours && animalCourant.actif)
{
boutonCloture =
"<button type=\"button\" class=\"secondaire\" data-cloturer=\"" +
typePeriode + "\" data-id=\"" + periode.id + "\">Clôturer</button>";
}
return (
"<tr>" +
"<td>" + periode.dateDebut + "</td>" +
"<td>" + (periode.dateFin || "—") + " " + statut + "</td>" +
"<td>" + (periode.remarque || "—") + "</td>" +
"<td>" + boutonCloture + "</td>" +
"</tr>"
);
}).join("");
return (
"<table class=\"tableau-periodes\">" +
"<thead><tr><th>Début</th><th>Fin</th><th>Remarque</th><th></th></tr></thead>" +
"<tbody>" + lignes + "</tbody>" +
"</table>"
);
}
function afficherPeriodes()
{
document.getElementById("listePonte").innerHTML = htmlTableauPeriodes(periodesPonte, "ponte");
document.getElementById("listeCouvage").innerHTML = htmlTableauPeriodes(periodesCouvage, "couvage");
document.querySelectorAll("[data-cloturer]").forEach(function (bouton)
{
bouton.addEventListener("click", async function ()
{
const type = bouton.getAttribute("data-cloturer");
const id = bouton.getAttribute("data-id");
const aujourdHui = new Date().toISOString().slice(0, 10);
const url = type === "ponte"
? "/api/periodes-ponte/" + id
: "/api/periodes-couvage/" + id;
await appelerApi(url, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ dateFin: aujourdHui })
});
await chargerFiche();
});
});
}
function ouvrirModification()
{
document.getElementById("animalId").value = animalCourant.id;
document.getElementById("type").value = animalCourant.type;
document.getElementById("couleurBague").value = animalCourant.couleurBague || "";
document.getElementById("numeroBague").value = animalCourant.numeroBague || "";
document.getElementById("dateNaissance").value = animalCourant.dateNaissance || "";
document.getElementById("etatSante").value = animalCourant.etatSante || "bon";
document.getElementById("photo").value = "";
document.getElementById("erreurAnimal").hidden = true;
document.getElementById("dialogueAnimal").showModal();
}
function ouvrirDeces()
{
document.getElementById("dateFinDeVie").value = new Date().toISOString().slice(0, 10);
document.getElementById("dialogueDeces").showModal();
}
function ouvrirPeriode(type)
{
document.getElementById("typePeriode").value = type;
document.getElementById("titrePeriode").textContent =
type === "ponte" ? "Nouvelle période de ponte" : "Nouvelle période de couvage";
document.getElementById("formPeriode").reset();
document.getElementById("typePeriode").value = type;
document.getElementById("dateDebut").value = new Date().toISOString().slice(0, 10);
document.getElementById("erreurPeriode").hidden = true;
document.getElementById("dialoguePeriode").showModal();
}
async function chargerFiche()
{
const id = recupererIdDepuisUrl();
if (!id)
{
window.location.href = "/";
return;
}
const donnees = await appelerApi("/api/animaux/" + id);
if (!donnees)
{
return;
}
animalCourant = donnees.animal;
periodesPonte = donnees.periodesPonte;
periodesCouvage = donnees.periodesCouvage;
afficherFiche();
}
document.getElementById("annulerAnimal").addEventListener("click", function ()
{
document.getElementById("dialogueAnimal").close();
});
document.getElementById("annulerDeces").addEventListener("click", function ()
{
document.getElementById("dialogueDeces").close();
});
document.getElementById("annulerPeriode").addEventListener("click", function ()
{
document.getElementById("dialoguePeriode").close();
});
document.getElementById("boutonAjouterPonte").addEventListener("click", function ()
{
ouvrirPeriode("ponte");
});
document.getElementById("boutonAjouterCouvage").addEventListener("click", function ()
{
ouvrirPeriode("couvage");
});
document.getElementById("formAnimal").addEventListener("submit", async function (event)
{
event.preventDefault();
const erreurAnimal = document.getElementById("erreurAnimal");
erreurAnimal.hidden = true;
const formData = new FormData();
formData.append("type", document.getElementById("type").value);
formData.append("couleurBague", document.getElementById("couleurBague").value);
formData.append("numeroBague", document.getElementById("numeroBague").value);
formData.append("dateNaissance", document.getElementById("dateNaissance").value);
formData.append("etatSante", document.getElementById("etatSante").value);
const photo = document.getElementById("photo").files[0];
if (photo)
{
formData.append("photo", photo);
}
try
{
await appelerApi("/api/animaux/" + animalCourant.id, {
method: "PUT",
body: formData
});
document.getElementById("dialogueAnimal").close();
await chargerFiche();
}
catch (erreur)
{
erreurAnimal.textContent = erreur.message;
erreurAnimal.hidden = false;
}
});
document.getElementById("formDeces").addEventListener("submit", async function (event)
{
event.preventDefault();
await appelerApi("/api/animaux/" + animalCourant.id + "/deces", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
dateFinDeVie: document.getElementById("dateFinDeVie").value
})
});
document.getElementById("dialogueDeces").close();
window.location.href = "/archives.html";
});
document.getElementById("formPeriode").addEventListener("submit", async function (event)
{
event.preventDefault();
const erreurPeriode = document.getElementById("erreurPeriode");
erreurPeriode.hidden = true;
const type = document.getElementById("typePeriode").value;
const url = type === "ponte" ? "/api/periodes-ponte" : "/api/periodes-couvage";
const dateFin = document.getElementById("dateFin").value || null;
try
{
await appelerApi(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
idAnimal: animalCourant.id,
dateDebut: document.getElementById("dateDebut").value,
dateFin: dateFin,
remarque: document.getElementById("remarque").value
})
});
document.getElementById("dialoguePeriode").close();
await chargerFiche();
}
catch (erreur)
{
erreurPeriode.textContent = erreur.message;
erreurPeriode.hidden = false;
}
});
async function demarrer()
{
const utilisateur = await verifierSession();
if (!utilisateur)
{
return;
}
brancherDeconnexion();
await chargerFiche();
}
demarrer();
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.