Les tags s'affichent en pastilles colorées dans la colonne des libellés, et une barre de filtres apparaît sous la barre d'outils dès qu'un tag existe. Cliquer un tag — dans cette barre ou directement sur la pastille d'un projet — restreint la frise ; recliquer le retire. Le filtre est conjonctif : un projet doit porter tous les tags cochés. Cocher un tag de plus resserre donc toujours la sélection, sans jamais faire se repeupler la frise. Un message dédié prend la place de la frise quand plus rien ne passe le filtre, sans quoi elle laisserait croire que les projets ont disparu. Le filtre est un état de vue, jamais enregistré, au même titre que la fenêtre temporelle. Il ne fait pas le même travail que le masquage par l'œil, qui reste enregistré : le filtre retire le projet de la vue entière, l'œil garde son libellé grisé et n'ôte que les barres. Deux ajustements de confort : créer un projet sous filtre actif préremplit ses tags avec ceux du filtre, sinon le nouveau venu naîtrait invisible ; et un tag que plus aucun projet ne porte est retiré du filtre, sinon il masquerait tout. Le dialogue d'un projet gagne un champ de saisie et propose en un clic les tags déjà employés ailleurs, seul garde-fou contre la dérive orthographique. Dans la colonne des libellés, les pastilles rognent le nom du projet mais lui laissent quatre caractères, et s'effacent à leur tour plutôt que de repousser l'œil et le menu hors de la colonne. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
190 lines
6.7 KiB
HTML
190 lines
6.7 KiB
HTML
<!doctype html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>Frise multi-projets</title>
|
||
<link rel="stylesheet" href="css/style.css" />
|
||
<link
|
||
rel="icon"
|
||
href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><rect width='16' height='16' rx='3' fill='%233b82f6'/><rect x='3' y='4' width='9' height='2' rx='1' fill='white'/><rect x='5' y='7' width='8' height='2' rx='1' fill='white'/><rect x='3' y='10' width='6' height='2' rx='1' fill='white'/></svg>"
|
||
/>
|
||
</head>
|
||
<body>
|
||
<header class="outils">
|
||
<h1>Frise multi-projets</h1>
|
||
|
||
<div class="outils__groupe">
|
||
<button type="button" id="aller-aujourdhui">Aujourd'hui</button>
|
||
<button type="button" id="nouveau-projet" class="bouton--principal">
|
||
Nouveau projet
|
||
</button>
|
||
</div>
|
||
|
||
<p class="outils__etat" id="etat-sauvegarde" role="status" aria-live="polite"></p>
|
||
</header>
|
||
|
||
<!--
|
||
Barre de filtres, masquée tant qu'aucun projet ne porte de tag : elle
|
||
n'apparaît que le jour où elle sert à quelque chose.
|
||
-->
|
||
<div class="filtres" id="filtres" hidden role="group" aria-label="Filtrer par tag">
|
||
<span class="filtres__titre">Filtrer</span>
|
||
<div class="tags tags--filtres" id="filtres-liste"></div>
|
||
<button type="button" id="effacer-filtres" hidden>Tout afficher</button>
|
||
</div>
|
||
|
||
<!--
|
||
Un seul conteneur défilant, découpé en grille 2×2 : l'en-tête de dates est
|
||
collé en haut (`sticky top`), la colonne des libellés collée à gauche
|
||
(`sticky left`), et le coin aux deux. Le navigateur gère ainsi le
|
||
défilement des deux axes sans aucune synchronisation en JavaScript.
|
||
-->
|
||
<main class="frise" id="frise">
|
||
<div class="grille">
|
||
<div class="grille__coin"></div>
|
||
<div class="grille__entete" id="entete"></div>
|
||
<div class="grille__libelles" id="libelles"></div>
|
||
<div class="grille__couloirs" id="couloirs"></div>
|
||
</div>
|
||
</main>
|
||
|
||
<p class="vide" id="message-vide" hidden>
|
||
Aucun projet pour l'instant.<br />
|
||
<button type="button" id="premier-projet" class="bouton--principal">
|
||
Créer un premier projet
|
||
</button>
|
||
</p>
|
||
|
||
<p class="vide" id="message-filtre" hidden>
|
||
Aucun projet ne porte tous les tags sélectionnés.<br />
|
||
<button type="button" id="effacer-filtres-vide">Tout afficher</button>
|
||
</p>
|
||
|
||
<!-- Panneau de détail, ouvert au clic sur une phase. -->
|
||
<aside class="panneau" id="panneau" hidden aria-label="Détail de la phase">
|
||
<div class="panneau__entete">
|
||
<h2 id="panneau-titre">Phase</h2>
|
||
<button type="button" id="fermer-panneau" aria-label="Fermer">×</button>
|
||
</div>
|
||
<form class="panneau__corps" id="formulaire-phase">
|
||
<label>
|
||
Nom
|
||
<input type="text" name="name" required autocomplete="off" />
|
||
</label>
|
||
|
||
<div class="champ-double">
|
||
<label>
|
||
Début
|
||
<input type="date" name="start" required />
|
||
</label>
|
||
<label id="champ-fin">
|
||
Fin
|
||
<input type="date" name="end" required />
|
||
</label>
|
||
</div>
|
||
|
||
<label>
|
||
Statut
|
||
<select name="status">
|
||
<option value="todo">À venir</option>
|
||
<option value="doing">En cours</option>
|
||
<option value="done">Terminé</option>
|
||
<option value="blocked">Bloqué</option>
|
||
</select>
|
||
</label>
|
||
|
||
<label class="case">
|
||
<input type="checkbox" name="milestone" />
|
||
Jalon (une seule date, affichée en losange)
|
||
</label>
|
||
|
||
<label>
|
||
Notes
|
||
<textarea name="notes" rows="4"></textarea>
|
||
</label>
|
||
|
||
<p class="panneau__erreur" id="erreur-phase" role="alert" hidden></p>
|
||
|
||
<div class="panneau__actions">
|
||
<button type="button" id="supprimer-phase" class="bouton--danger">Supprimer</button>
|
||
</div>
|
||
</form>
|
||
</aside>
|
||
|
||
<div class="voile" id="voile" hidden></div>
|
||
|
||
<!-- Menu contextuel, positionné à l'ouverture près de son point d'ancrage. -->
|
||
<div class="menu" id="menu" hidden role="menu"></div>
|
||
|
||
<!-- Création et renommage d'un projet. -->
|
||
<dialog class="dialogue" id="dialogue-projet">
|
||
<form method="dialog">
|
||
<h2 id="dialogue-projet-titre">Nouveau projet</h2>
|
||
|
||
<label>
|
||
Nom
|
||
<input type="text" id="dialogue-projet-nom" required autocomplete="off" />
|
||
</label>
|
||
|
||
<fieldset class="couleurs">
|
||
<legend>Couleur du couloir</legend>
|
||
<div id="dialogue-projet-couleurs"></div>
|
||
</fieldset>
|
||
|
||
<label>
|
||
Tags, séparés par des virgules
|
||
<input
|
||
type="text"
|
||
id="dialogue-projet-tags"
|
||
autocomplete="off"
|
||
placeholder="client, refonte"
|
||
/>
|
||
</label>
|
||
|
||
<!-- Masqué tant qu'aucun tag n'existe ailleurs dans le planning. -->
|
||
<fieldset class="couleurs" id="dialogue-projet-suggestions-bloc" hidden>
|
||
<legend>Tags déjà utilisés</legend>
|
||
<div class="tags tags--suggestions" id="dialogue-projet-suggestions"></div>
|
||
</fieldset>
|
||
|
||
<p class="dialogue__erreur" id="dialogue-projet-erreur" role="alert" hidden></p>
|
||
|
||
<menu class="dialogue__actions">
|
||
<!--
|
||
`formnovalidate` est indispensable : le champ « Nom » est `required`,
|
||
et sans lui la validation HTML refuserait la soumission — donc la
|
||
fermeture du dialogue — tant qu'aucun nom n'est saisi. On se
|
||
retrouverait incapable d'annuler une création de projet.
|
||
-->
|
||
<button type="submit" value="annuler" formnovalidate>Annuler</button>
|
||
<button type="submit" value="ok" class="bouton--principal" id="dialogue-projet-ok">
|
||
Créer
|
||
</button>
|
||
</menu>
|
||
</form>
|
||
</dialog>
|
||
|
||
<!-- Confirmation d'une action irréversible. -->
|
||
<dialog class="dialogue" id="dialogue-confirmation">
|
||
<form method="dialog">
|
||
<h2 id="dialogue-confirmation-titre">Confirmer</h2>
|
||
<p id="dialogue-confirmation-message"></p>
|
||
<menu class="dialogue__actions">
|
||
<button type="submit" value="annuler">Annuler</button>
|
||
<button type="submit" value="ok" class="bouton--principal" id="dialogue-confirmation-ok">
|
||
Confirmer
|
||
</button>
|
||
</menu>
|
||
</form>
|
||
</dialog>
|
||
|
||
<!-- Bandeau d'erreur bloquante : fichier corrompu, serveur injoignable. -->
|
||
<div class="alerte" id="alerte" hidden role="alert">
|
||
<p id="alerte-message"></p>
|
||
</div>
|
||
|
||
<script type="module" src="js/app.js"></script>
|
||
</body>
|
||
</html>
|