From a75b9242f1a8666c7171a840be9a9d4e23ff7bf4 Mon Sep 17 00:00:00 2001 From: Christophe Robillard Date: Sat, 7 Feb 2026 12:09:06 +0100 Subject: [PATCH] group moods by month --- app/assets/images/unknown.jpg | Bin 0 -> 37511 bytes app/models/user.rb | 41 +-------------- app/services/mood_calendar_service.rb | 72 ++++++++++++++++++++++++++ app/views/moods/index.html.erb | 26 +++++----- 4 files changed, 85 insertions(+), 54 deletions(-) create mode 100644 app/assets/images/unknown.jpg create mode 100644 app/services/mood_calendar_service.rb diff --git a/app/assets/images/unknown.jpg b/app/assets/images/unknown.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4afdfc62a9669a787de878a397f96f74b2886f03 GIT binary patch literal 37511 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 :from", user_id: self.id, from:).first.recorded_at.to_date - end - - def monday_of_the_week(date) - date - date.wday + 1 - end - - def last_mode_before(last) - mood = Mood.where("user_id = :user_id AND recorded_at < :last", user_id: self.id, last: last).last - mood&.mode || "croisiere" + MoodCalendarService.generate_calendar(moods) end end diff --git a/app/services/mood_calendar_service.rb b/app/services/mood_calendar_service.rb new file mode 100644 index 0000000..930a357 --- /dev/null +++ b/app/services/mood_calendar_service.rb @@ -0,0 +1,72 @@ +# app/services/mood_calendar_service.rb +class MoodCalendarService + def self.generate_calendar(moods, start_date: nil, end_date: Date.current) + # Convertir la relation ActiveRecord en tableau de hash + data = moods.order(:recorded_at) + .pluck(:mode, :recorded_at) + .map { |mode, recorded_at| { mode: mode, recorded_at: recorded_at } } + + return [] if data.empty? + + # Définir start_date par défaut comme le recorded_at du premier mood + start_date ||= data.first[:recorded_at].to_date + + # Convertir en Date si ce sont des DateTime ou Time + start_date = start_date.to_date + end_date = end_date.to_date + + # Grouper par jour et garder le plus récent pour chaque jour + data_by_date = data.group_by { |d| d[:recorded_at].to_date } + .transform_values { |entries| entries.max_by { |e| e[:recorded_at] } } + + # Trouver le dernier mood avant start_date pour initialiser le guess + last_mode = data.select { |d| d[:recorded_at].to_date < start_date } + .max_by { |d| d[:recorded_at] } + &.[](:mode) + + # Générer le tableau complet avec tous les jours + complete_data = (start_date..end_date).map do |date| + if data_by_date[date] + last_mode = data_by_date[date][:mode] + data_by_date[date] + else + { mode: nil, recorded_at: date.to_datetime, guess: last_mode } + end + end + + # Regrouper par mois avec semaines commençant au premier lundi + complete_data.group_by { |d| d[:recorded_at].to_date.beginning_of_month } + .map do |month_start, month_data| + # Trouver le premier lundi du mois (à partir du 1er du mois) + first_monday = month_start + first_monday = first_monday.next_occurring(:monday) unless first_monday.monday? + + # Trouver le premier lundi du mois SUIVANT + next_month_start = month_start.next_month.beginning_of_month + next_first_monday = next_month_start + next_first_monday = next_first_monday.next_occurring(:monday) unless next_first_monday.monday? + + # Le dernier jour du mois est le dimanche précédant le premier lundi du mois suivant + month_end = next_first_monday - 1.day + + # Créer un hash pour accès rapide aux données de complete_data (qui contient déjà les guess) + data_hash = complete_data.index_by { |d| d[:recorded_at].to_date } + + # Générer tous les jours du premier lundi jusqu'au dimanche avant le prochain lundi + all_days = (first_monday..month_end).map do |date| + # Utiliser directement les données de complete_data qui ont déjà le bon guess + data_hash[date] || { mode: nil, recorded_at: date.to_datetime, guess: nil } + end + + # Grouper par semaines + weeks = all_days.group_by { |d| d[:recorded_at].to_date.beginning_of_week(:monday) } + .sort_by { |week_start, _| week_start } + .map { |_, week_moods| week_moods } + + { + month: month_start, + weeks: weeks + } + end + end +end diff --git a/app/views/moods/index.html.erb b/app/views/moods/index.html.erb index ce29b8b..4a803ed 100644 --- a/app/views/moods/index.html.erb +++ b/app/views/moods/index.html.erb @@ -38,22 +38,20 @@
-
- <% @history.each do |week| %> -
-
- <% mood = week.first %> - <% if mood[:day].day < 8 %> - <%= l(mood[:day], format: "%b") %> +
+ <% @history.each do |month| %> +
+
<%= I18n.l(month[:month], format: "%B %Y").capitalize %>
+
+ <% month[:weeks].each do |week| %> +
+ <% week.each do |mood| %> + <% mode = mood[:mode] || mood[:guess] || "unknown" %> +
" data-mode="<%= mode %>" data-day="<%= l mood[:recorded_at] %>" data-action="click->mood#updateDayInfo mouseover->mood#updateDayInfo mouseleave->mood#removeFeedback" title="<%= mood[:recorded_at] %> : <%= mode %>" class="day <%= mode %>">
+ <% end %> +
<% end %>
- <% week.each do |d| %> - <% if mood[:mode] %> -
" data-mode="<%= mood[:mode] %>" data-day="<%= l mood[:day] %>" data-action="click->mood#updateDayInfo mouseover->mood#updateDayInfo mouseleave->mood#removeFeedback" title="<%= mood[:day] %> : <%= mood[:mode] %>" class="day <%= mood[:mode] %>">
- <% else %> -
- <% end %> - <% end %>
<% end %>