let visitors pick a day on the public page
Some checks are pending
CI / scan_ruby (push) Waiting to run
CI / scan_js (push) Waiting to run
CI / lint (push) Waiting to run
CI / test (push) Waiting to run

Clicking a day shows its mode and illustration in the header, with a link
back to today. The journal stays private: the calendar helper now separates
interactivity from journal exposure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Christophe Robillard 2026-09-29 07:54:25 +02:00
parent 7a7fa4c095
commit 764cb542ac
6 changed files with 101 additions and 28 deletions

View file

@ -27,6 +27,21 @@ input.bouton {
cursor: pointer;
}
/* Bouton d'action présenté comme un lien de la charte */
button.lien {
padding: 0;
border: 0;
background: none;
color: var(--encre);
font: inherit;
cursor: pointer;
text-decoration-line: underline;
text-decoration-color: var(--vert);
text-decoration-thickness: 0.12em;
text-underline-offset: 0.2em;
}
button.lien:hover { text-decoration-color: var(--encre); }
/* button_to enveloppe son bouton dans un formulaire : le garder en ligne */
.nav form.button_to { display: inline; }
@ -371,6 +386,8 @@ button.jour:focus-visible {
font-size: 1.1rem;
}
.public-autre-jour .detail-statut:not(:empty) { margin-right: 0.75rem; }
.public-historique h2 {
margin: 2.5rem 0 0.75rem;
font-size: 1.35rem;

View file

@ -45,12 +45,13 @@ module MoodsHelper
end
end
# Un jour du calendrier : un bouton pour le propriétaire, une simple case pour les visiteurs.
def case_jour(mood, user, interactive:, selected: false)
# Un jour du calendrier : un bouton si on peut le choisir, sinon une simple case.
# Le journal n'est exposé que s'il est demandé (jamais sur la page publique).
def case_jour(mood, user, interactive:, selected: false, journal: false)
date = mood[:recorded_at].to_date
status = day_status(mood, user)
mode = mode_for(mood, user)
day_log = mood[:day_log] if interactive
day_log = mood[:day_log] if journal
description = [ date_longue(date), mode&.label, libelle_statut(status), ("journal écrit" if day_log) ].compact.join(", ")
classes = [ "jour", "jour-#{status}" ]

View file

@ -1,14 +1,17 @@
import { Controller } from "@hotwired/stimulus"
// Calendrier des humeurs : choisir un jour affiche son détail (illustration, mode, journal).
// Calendrier des humeurs : choisir un jour affiche son détail (illustration, mode, et
// sur le tableau de bord, le journal). Sert au tableau de bord comme à la page publique :
// chaque cible est facultative.
// Au clavier, les flèches se déplacent d'un jour (haut, bas) ou d'une semaine (gauche, droite).
export default class extends Controller {
static targets = [ "carte", "image", "date", "mode", "statut", "journal", "lien" ]
static targets = [ "carte", "image", "date", "qui", "mode", "statut", "depuis", "retour", "journal", "lien" ]
static values = { nom: String, aujourdhui: String, aujourdhuiMode: String, aujourdhuiImage: String, aujourdhuiCouleur: String }
connect() {
const selectionne = this.element.querySelector(".jour[aria-pressed='true']")
if (selectionne && new URLSearchParams(window.location.search).has("day")) {
selectionne.scrollIntoView({ block: "center" })
selectionne.scrollIntoView({ block: "nearest" })
}
}
@ -16,6 +19,11 @@ export default class extends Controller {
this.selectionner(event.currentTarget)
}
revenir() {
const aujourdhui = this.element.querySelector(`button.jour[data-day="${this.aujourdhuiValue}"]`)
if (aujourdhui) this.selectionner(aujourdhui)
}
naviguer(event) {
const decalages = { ArrowUp: -1, ArrowDown: 1, ArrowLeft: -7, ArrowRight: 7 }
const decalage = decalages[event.key]
@ -40,17 +48,42 @@ export default class extends Controller {
jour.setAttribute("aria-pressed", "true")
jour.tabIndex = 0
// Sur la page publique, aujourd'hui montre la dernière tasse posée et depuis quand
if (jour.dataset.day === this.aujourdhuiValue && this.hasDepuisTarget) {
this.afficherAujourdhui()
return
}
const { day, date, mode, statut, couleur, image, info } = jour.dataset
this.carteTarget.style.setProperty("--humeur", couleur || "")
this.imageTarget.src = image
this.dateTarget.textContent = date
this.modeTarget.textContent = mode || "Pas de tasse posée"
this.statutTarget.textContent = mode ? (statut || "") : ""
if (this.hasDateTarget) this.dateTarget.textContent = date
if (this.hasQuiTarget) this.quiTarget.textContent = `Le ${date} chez ${this.nomValue}`
if (this.hasDepuisTarget) this.depuisTarget.hidden = true
if (this.hasRetourTarget) this.retourTarget.hidden = false
if (this.hasJournalTarget) {
this.journalTarget.textContent = info || this.journalTarget.dataset.vide
this.journalTarget.classList.toggle("detail-journal-vide", !info)
}
if (this.hasLienTarget) {
this.lienTarget.href = `/day_logs/edit?day=${day}`
this.lienTarget.textContent = info ? "Modifier le journal" : "Écrire dans le journal"
}
history.replaceState({}, "", `?day=${day}`)
}
afficherAujourdhui() {
this.carteTarget.style.setProperty("--humeur", this.aujourdhuiCouleurValue)
this.imageTarget.src = this.aujourdhuiImageValue
this.modeTarget.textContent = this.aujourdhuiModeValue
this.quiTarget.textContent = `Aujourd’hui chez ${this.nomValue}`
this.depuisTarget.hidden = false
this.retourTarget.hidden = true
history.replaceState({}, "", window.location.pathname)
}
}

View file

@ -1,7 +1,7 @@
<%# Calendrier des humeurs « à la GitHub » : une colonne par semaine, une ligne par jour,
regroupé par mois. Par défaut, un bloc par année, la plus récente d'abord ;
avec par_annee: false, tous les mois à la suite (l'année est ajoutée aux mois d'une autre année). %>
<%# locals: (history:, user:, interactive: false, selected_date: nil, par_annee: true) -%>
<%# locals: (history:, user:, interactive: false, journal: false, selected_date: nil, par_annee: true) -%>
<% blocs = par_annee ? history_by_year(history) : [ [ nil, history ] ] %>
<div class="calendrier-humeurs">
<% blocs.each do |year, months| %>
@ -18,7 +18,7 @@
<h3 class="mois-nom"><%= l(month[:month], format: year || month[:month].year == Date.current.year ? "%B" : "%B %Y") %></h3>
<div class="mois-jours">
<% month[:weeks].flatten.each do |mood| %>
<%= case_jour(mood, user, interactive: interactive,
<%= case_jour(mood, user, interactive: interactive, journal: journal,
selected: mood[:recorded_at].to_date == selected_date) %>
<% end %>
</div>

View file

@ -9,7 +9,7 @@
<div class="tableau-calendrier">
<h1 class="tableau-titre">Mes humeurs</h1>
<%= render "moods/legende", user: @user, journal: true %>
<%= render "moods/calendrier", history: history, user: @user, interactive: true, selected_date: selected_date %>
<%= render "moods/calendrier", history: history, user: @user, interactive: true, journal: true, selected_date: selected_date %>
</div>
<aside class="detail" aria-live="polite" aria-label="Jour sélectionné">

View file

@ -1,30 +1,52 @@
<% content_for :title, "Comment ça Kluk chez #{@user.username} ?" %>
<% mode = @user.moods.last&.mode %>
<% history = MoodCalendarService.generate_calendar(@user, start_date: 5.months.ago.beginning_of_month, end_date: Date.current) %>
<% selected_date = (Date.parse(params[:day]) rescue Date.current) %>
<% selected = find_day(history, selected_date) if selected_date < Date.current %>
<% selected_date = Date.current unless selected %>
<% aujourdhui = selected_date == Date.current %>
<% mode_du_jour = @user.moods.last&.mode %>
<% mode = aujourdhui ? mode_du_jour : mode_for(selected, @user) %>
<% status = day_status(selected, @user) unless aujourdhui %>
<% since = @user.current_mode_since %>
<article class="public">
<figure class="carte-humeur carte-humeur-grande" style="<%= "--humeur: #{mode.color}" if mode %>">
<%= image_tag (mode || Mode.new).image_url, alt: "" %>
<article class="public" data-controller="calendrier"
data-calendrier-nom-value="<%= @user.username %>"
data-calendrier-aujourdhui-value="<%= Date.current.iso8601 %>"
data-calendrier-aujourdhui-mode-value="<%= mode_du_jour&.label || "Pas de tasse posée" %>"
data-calendrier-aujourdhui-image-value="<%= (mode_du_jour || Mode.new).image_url %>"
data-calendrier-aujourdhui-couleur-value="<%= mode_du_jour&.color %>">
<figure class="carte-humeur carte-humeur-grande" data-calendrier-target="carte" style="<%= "--humeur: #{mode.color}" if mode %>">
<%= image_tag (mode || Mode.new).image_url, alt: "", data: { calendrier_target: "image" } %>
</figure>
<div class="public-texte">
<p class="public-qui">Aujourd’hui chez <%= @user.username %></p>
<h1 class="public-mode"><%= mode&.label || "Pas encore de tasse posée" %></h1>
<% if since %>
<p class="public-depuis">
<% if since == Date.current %>
Depuis ce matin.
<div class="public-texte" aria-live="polite">
<p class="public-qui" data-calendrier-target="qui">
<% if aujourdhui %>
Aujourd’hui chez <%= @user.username %>
<% else %>
Depuis le <%= l(since, format: since.year == Date.current.year ? "%A %-d %B" : "%-d %B %Y") %>.
Le <%= date_longue(selected_date) %> chez <%= @user.username %>
<% end %>
</p>
<h1 class="public-mode" data-calendrier-target="mode"><%= mode&.label || "Pas de tasse posée" %></h1>
<p class="public-depuis">
<span data-calendrier-target="depuis" <%= "hidden" unless aujourdhui %>>
<% if since == Date.current %>
Depuis ce matin.
<% elsif since %>
Depuis le <%= l(since, format: since.year == Date.current.year ? "%A %-d %B" : "%-d %B %Y") %>.
<% end %>
</span>
<span class="public-autre-jour" data-calendrier-target="retour" <%= "hidden" if aujourdhui %>>
<span class="detail-statut" data-calendrier-target="statut"><%= libelle_statut(status) if mode %></span>
<button type="button" class="lien" data-action="calendrier#revenir">Revenir à aujourd’hui</button>
</span>
</p>
<section class="public-historique" aria-labelledby="public-historique-titre">
<h2 id="public-historique-titre">Les derniers mois</h2>
<%= render "moods/legende", user: @user %>
<%= render "moods/calendrier", user: @user, par_annee: false,
history: MoodCalendarService.generate_calendar(@user, start_date: 5.months.ago.beginning_of_month, end_date: Date.current) %>
<%= render "moods/calendrier", history: history, user: @user, par_annee: false,
interactive: true, selected_date: selected_date %>
</section>
</div>
</article>