Une phase avait quatre lignes de texte brut ; un projet n'avait rien. Les deux
manques se tiennent : la frise ne dit pas qui pilote ni sur quel budget, et ce
contexte finissait dans les notes de la première phase venue, où il ne survit
pas à sa suppression.
Le bloc de notes est le même dans les deux panneaux et prend toute la hauteur
restante : les autres champs ont une taille dictée par leur contenu, une note
fait ce qu'on a à dire. Il s'ouvre sur l'aperçu quand la note existe, sur la
saisie quand elle est vide.
Les cases à cocher se cliquent dans l'aperçu — seul geste de l'aperçu qui touche
aux données. La bascule réécrit la seule ligne visée plutôt que de régénérer la
note, qui verrait sinon sa mise en forme normalisée. Inertes dans la vue par
mois, en lecture seule (décision 24).
La barre d'outils écrit par `document.execCommand('insertText')` et non en
affectant `value`, qui viderait la pile d'annulation du navigateur : `Ctrl+Z`
doit continuer de marcher. Le DOM est bâti nœud par nœud, sans un `innerHTML`.
Les infobulles aplatissent le markdown — `title` ne connaît que le texte —, et
celles d'un jalon et d'un libellé de projet les affichent désormais.
Le format passe en version 4 : le validateur reconstruit chaque projet champ par
champ, un binaire antérieur effacerait les notes de projet à la première
sauvegarde.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
241 lines
9.2 KiB
HTML
241 lines
9.2 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>
|
||
|
||
<!--
|
||
Deux regards sur les mêmes données : la frise, qui montre les durées et
|
||
les chevauchements, et la liste par mois, qui montre l'ordre du
|
||
calendrier tous projets mêlés. Le filtre par tags, lui, vaut pour les
|
||
deux — d'où sa place inchangée juste en dessous.
|
||
-->
|
||
<!--
|
||
Deux boutons à deux états plutôt que le motif ARIA « tablist » : celui-ci
|
||
promet une navigation aux flèches, or les flèches servent déjà à décaler
|
||
la phase sélectionnée. Mieux vaut ne rien annoncer qu'annoncer faux.
|
||
-->
|
||
<div class="onglets" id="onglets" role="group" aria-label="Vue">
|
||
<button type="button" data-vue="frise" id="onglet-frise">Frise</button>
|
||
<button type="button" data-vue="mois" id="onglet-mois">Par mois</button>
|
||
</div>
|
||
|
||
<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">
|
||
<!--
|
||
Poignée de largeur de la colonne. Placée dans le coin, seul endroit
|
||
de la colonne qui reste visible quel que soit le défilement.
|
||
-->
|
||
<div
|
||
class="poignee-colonne"
|
||
id="poignee-colonne"
|
||
role="separator"
|
||
aria-orientation="vertical"
|
||
aria-label="Largeur de la colonne des projets"
|
||
tabindex="0"
|
||
title="Glisser pour élargir la colonne — double-clic pour la réinitialiser"
|
||
></div>
|
||
</div>
|
||
<div class="grille__entete" id="entete"></div>
|
||
<div class="grille__libelles" id="libelles"></div>
|
||
<div class="grille__couloirs" id="couloirs"></div>
|
||
</div>
|
||
</main>
|
||
|
||
<!--
|
||
Vue par mois. Elle remplace la frise plutôt que de la flanquer : les deux
|
||
veulent toute la largeur, l'une pour dérouler le temps, l'autre pour poser
|
||
côte à côte la date, le nom, le projet et le statut.
|
||
-->
|
||
<section class="mois" id="vue-mois" hidden aria-label="Tâches et jalons par mois"></section>
|
||
|
||
<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. Volontairement **non
|
||
modal** : la frise reste vivante derrière lui, on passe d'une phase à
|
||
l'autre en cliquant sa barre. `tabindex="-1"` permet d'y poser le focus à
|
||
l'ouverture sans le placer dans un champ de saisie, ce qui laisse les
|
||
flèches piloter la phase sélectionnée.
|
||
-->
|
||
<aside class="panneau" id="panneau" hidden tabindex="-1" 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>
|
||
|
||
<!--
|
||
Les notes sont bâties par `notes.js` : aperçu markdown, barre d'outils
|
||
et zone de saisie, le tout identique dans les deux panneaux. Un seul
|
||
point d'ancrage ici plutôt que deux fois la même douzaine de balises.
|
||
-->
|
||
<section id="notes-phase"></section>
|
||
|
||
<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>
|
||
|
||
<!-- Menu contextuel, positionné à l'ouverture près de son point d'ancrage. -->
|
||
<div class="menu" id="menu" hidden role="menu"></div>
|
||
|
||
<!--
|
||
Paramètres d'un projet. Même panneau, mêmes gestes et même édition en
|
||
direct que le détail d'une phase : renommer un projet et renommer une
|
||
phase sont la même opération à un niveau près, elles n'ont pas à se
|
||
présenter différemment.
|
||
-->
|
||
<aside
|
||
class="panneau"
|
||
id="panneau-projet"
|
||
hidden
|
||
tabindex="-1"
|
||
aria-label="Paramètres du projet"
|
||
>
|
||
<div class="panneau__entete">
|
||
<h2 id="panneau-projet-titre">Projet</h2>
|
||
<button type="button" id="fermer-panneau-projet" aria-label="Fermer">×</button>
|
||
</div>
|
||
<form class="panneau__corps" id="formulaire-projet">
|
||
<label>
|
||
Nom
|
||
<input type="text" name="name" required autocomplete="off" />
|
||
</label>
|
||
|
||
<fieldset class="couleurs">
|
||
<legend>Couleur du couloir</legend>
|
||
<div id="panneau-projet-couleurs"></div>
|
||
</fieldset>
|
||
|
||
<label>
|
||
Tags, séparés par des virgules
|
||
<input type="text" name="tags" autocomplete="off" placeholder="client, refonte" />
|
||
</label>
|
||
|
||
<!-- Masqué tant qu'aucun tag n'existe ailleurs dans le planning. -->
|
||
<fieldset class="couleurs" id="panneau-projet-suggestions-bloc" hidden>
|
||
<legend>Tags déjà utilisés</legend>
|
||
<div class="tags tags--suggestions" id="panneau-projet-suggestions"></div>
|
||
</fieldset>
|
||
|
||
<!--
|
||
Notes du projet : ce que la frise ne sait pas dire. Le contexte d'un
|
||
engagement, un interlocuteur, une décision et sa date. Elles occupent
|
||
le bas du volet, là où celles d'une phase occupent le bas du sien.
|
||
-->
|
||
<section id="notes-projet"></section>
|
||
|
||
<p class="panneau__erreur" id="erreur-projet" role="alert" hidden></p>
|
||
|
||
<div class="panneau__actions">
|
||
<button type="button" id="supprimer-projet" class="bouton--danger">
|
||
Supprimer le projet
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</aside>
|
||
|
||
<!-- 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>
|