Compare commits

..

No commits in common. "1a6d60943a4b1d40a3818712312fe6e38f92e5fd" and "939668c4fb1f9977df452337d179f96b5f1dd4bb" have entirely different histories.

30 changed files with 341 additions and 927 deletions

View file

@ -42,7 +42,32 @@ main {
align-content: center; align-content: center;
} }
.bar { .bar-afond {
background: black;
width: 10px;
height: 10px;
}
.bar-creatif {
background: red;
width: 10px;
height: 10px;
}
.bar-frigo {
background: gray;
width: 10px;
height: 10px;
}
.bar-croisiere {
background: green;
width: 10px;
height: 10px;
}
.bar-en-charge {
background: orange;
width: 10px; width: 10px;
height: 10px; height: 10px;
} }
@ -55,19 +80,35 @@ main {
} }
.selected-day { .selected-day {
border-radius: 50%; border: 2px double white;
box-shadow: 0 0 0 3px white, 0 0 0 5px black;
margin: 4px; margin: 4px;
width: 15px; width: 15px;
height: 15px; height: 15px;
} }
.empty-day { .creatif {
border: 1px dashed #aaa; background-color: red;
margin: 4px; }
width: 15px;
height: 15px; .unknown {
background: transparent; border: 2px double grey;
background-color: white;
}
.en-charge {
background-color: orange;
}
.frigo-vide {
background-color: grey;
}
.croisiere {
background-color: green;
}
.afond {
background-color: black;
} }
.info { .info {

View file

@ -1,50 +0,0 @@
class DayLogsController < ApplicationController
before_action :set_day_log, only: [ :update ]
def edit
@day_log = Current.user.day_logs.find_or_initialize_by(day: params[:day])
@mood = Current.user.moods.find_by(recorded_at: params[:day].to_date.beginning_of_day..params[:day].to_date.end_of_day)
end
def create
@day_log = Current.user.day_logs.build(day_log_params)
if @day_log.save
handle_mood(@day_log.day, params[:day_log][:mode_id])
redirect_to dashboard_path(day: @day_log.day)
else
render :new, status: :unprocessable_content
end
end
def update
if @day_log.update(day_log_params)
handle_mood(@day_log.day, params[:day_log][:mode_id])
redirect_to dashboard_path(day: @day_log.day)
else
render :edit, status: :unprocessable_content
end
end
private
def set_day_log
@day_log = Current.user.day_logs.find(params[:id])
end
def handle_mood(day, mode_id)
return if mode_id.blank?
mood = Current.user.moods.find_by(recorded_at: day.beginning_of_day..day.end_of_day)
if mood
mood.update(mode_id: mode_id)
else
Current.user.moods.create(mode_id: mode_id, recorded_at: day.to_datetime)
end
end
def day_log_params
params.expect(day_log: [ :day, :info ])
end
end

View file

@ -1,6 +1,5 @@
class MoodsController < ApplicationController class MoodsController < ApplicationController
def index def index
@user = Current.user @user = Current.user
redirect_to dashboard_path(day: Date.today) unless params[:day]
end end
end end

View file

@ -1,37 +1,9 @@
module MoodsHelper module MoodsHelper
def day_status(mood, user)
return :filled if mood[:mode].present?
return :guessed if mood[:guess].present? && user.guess?
return :unknown if mood[:guess].present? && !user.guess?
:empty
end
def mode_for(mood, user)
case day_status(mood, user)
when :filled then mood[:mode]
when :guessed then mood[:guess]
when :unknown, :empty then Mode.new(label: "unknown", color: "white")
end
end
=begin
def mode_for(mood, user) def mode_for(mood, user)
if user.guess? if user.guess?
mood[:mode] || mood[:guess] || { label: "unknown", color: "white", image_url: "unknown.jpg" } mood[:mode] || mood[:guess] || "unknown"
else else
mood[:mode] || { label: "unknown", color: "white", image_url: "unknown.jpg" } mood[:mode] || "unknown"
end end
end end
=end
def style_for_mode(mode, status)
case status
when :empty then ""
when :unknown then "background-color: #{mode.color}; border: 2px double grey;"
else "background-color: #{mode.color};"
end
end
def css_class_for_day(status)
status == :empty ? "day empty-day" : "day"
end
end end

View file

@ -1,44 +1,18 @@
import { Controller } from '@hotwired/stimulus' import { Controller } from '@hotwired/stimulus'
export default class extends Controller { export default class extends Controller {
static targets = [ "image", "modeDay", "dayLogLink", "dayLogInfo" ]; static targets = [ "image", "modeDay" ];
connect() {
this.lastTarget = null;
const params = new URLSearchParams(window.location.search);
const selectedDay = params.get("day");
if (selectedDay) {
const dayEl = document.querySelector(`[data-day="${selectedDay}"]`);
if (dayEl) {
dayEl.click();
dayEl.scrollIntoView({ block: "center" });
}
} else {
const logs = document.querySelector(".logs");
if (logs) logs.scrollTop = logs.scrollHeight;
}
}
updateDayInfo(event) { updateDayInfo(event) {
const image = this.imageTarget; const image = this.imageTarget;
const modeDay = this.modeDayTarget; const modeDay = this.modeDayTarget;
const day = event.target.dataset.day; const modeDayContent = event.target.dataset.day + ' : ' + event.target.dataset.mode;
const formattedDay = new Date(day).toLocaleDateString('fr-FR');
const modeDayContent = formattedDay + ' : ' + event.target.dataset.mode;
image.src = event.target.dataset.image; image.src = event.target.dataset.image;
modeDay.textContent = modeDayContent; modeDay.textContent = modeDayContent;
this.dayLogLinkTarget.href = `/day_logs/edit?day=${day}`;
this.dayLogInfoTarget.textContent = event.target.dataset.info || '';
event.target.className = "selected-day " + event.target.dataset.mode; event.target.className = "selected-day " + event.target.dataset.mode;
}
history.pushState({}, '', `?day=${day}`); connect() {
window.location = "#end";
if (this.lastTarget) {
this.lastTarget.className = "day " + this.lastTarget.dataset.mode;
}
this.lastTarget = event.target;
} }
} }

View file

@ -1,6 +0,0 @@
class DayLog < ApplicationRecord
belongs_to :user
validates :day, presence: true
validates :info, presence: true
end

View file

@ -1,8 +0,0 @@
class Mode < ApplicationRecord
belongs_to :user
has_one_attached :image
def image_url
image.attached? ? Rails.application.routes.url_helpers.rails_blob_path(image, only_path: true) : ActionController::Base.helpers.asset_path("unknown.jpg")
end
end

View file

@ -1,19 +1,3 @@
class Mood < ApplicationRecord class Mood < ApplicationRecord
belongs_to :user belongs_to :user
belongs_to :mode
validates :recorded_at, presence: true
validate :unique_per_day_and_user
private
def unique_per_day_and_user
return unless recorded_at && user
existing = user.moods.where.not(id: id).any? do |mood|
mood.recorded_at.to_date == recorded_at.to_date
end
errors.add(:recorded_at, :taken) if existing
end
end end

View file

@ -2,8 +2,6 @@ class User < ApplicationRecord
has_secure_password has_secure_password
has_many :sessions, dependent: :destroy has_many :sessions, dependent: :destroy
has_many :moods, -> { order "recorded_at" } has_many :moods, -> { order "recorded_at" }
has_many :modes
has_many :day_logs
normalizes :email_address, with: ->(e) { e.strip.downcase } normalizes :email_address, with: ->(e) { e.strip.downcase }
@ -33,14 +31,10 @@ class User < ApplicationRecord
end end
def history def history
MoodCalendarService.generate_calendar(self) MoodCalendarService.generate_calendar(moods)
end end
def current_mode def current_mood
self.moods.last&.mode || "croisiere" self.moods.last&.mode || "croisiere"
end end
def current_day_log
self.day_logs.find_by(day: Date.today)
end
end end

View file

@ -1,10 +1,11 @@
# app/services/mood_calendar_service.rb
class MoodCalendarService class MoodCalendarService
def self.generate_calendar(user, start_date: nil, end_date: nil) def self.generate_calendar(moods, start_date: nil, end_date: Date.current)
data = user.moods.includes(:mode) # Convertir la relation ActiveRecord en tableau de hash
.order(:recorded_at) data = moods.order(:recorded_at)
.map { |mood| { mode: mood.mode, recorded_at: mood.recorded_at } } .pluck(:mode, :recorded_at)
.map { |mode, recorded_at| { mode: mode, recorded_at: recorded_at } }
day_logs_by_date = user.day_logs.index_by(&:day)
if data.empty? if data.empty?
start_date = Date.current start_date = Date.current
@ -12,58 +13,66 @@ class MoodCalendarService
start_date ||= data.first[:recorded_at].to_date start_date ||= data.first[:recorded_at].to_date
end end
start_date = start_date.to_date if end_date < (start_date + 5.months)
end_date = end_date ? end_date.to_date : [ Date.current, start_date + 5.months ].max
=begin
if end_date.nil?
end_date = start_date + 5.months end_date = start_date + 5.months
else
end_date = end_date.to_date
end end
=end
# 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 } data_by_date = data.group_by { |d| d[:recorded_at].to_date }
.transform_values { |entries| entries.max_by { |e| e[:recorded_at] } } .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 } last_mode = data.select { |d| d[:recorded_at].to_date < start_date }
.max_by { |d| d[:recorded_at] } .max_by { |d| d[:recorded_at] }
&.[](:mode) &.[](:mode)
# Générer le tableau complet avec tous les jours
complete_data = (start_date..end_date).map do |date| complete_data = (start_date..end_date).map do |date|
day_log = day_logs_by_date[date]
if data_by_date[date] if data_by_date[date]
last_mode = data_by_date[date][:mode] last_mode = data_by_date[date][:mode]
data_by_date[date].merge(day_log: day_log) data_by_date[date]
else else
{ mode: nil, recorded_at: date.to_datetime, guess: last_mode, day_log: day_log } { mode: nil, recorded_at: date.to_datetime, guess: last_mode }
end end
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 } complete_data.group_by { |d| d[:recorded_at].to_date.beginning_of_month }
.map do |month_start, _| .map do |month_start, month_data|
first_monday = month_start # Trouver le premier lundi du mois (à partir du 1er du mois)
first_monday = first_monday.next_occurring(:monday) unless first_monday.monday? first_monday = month_start
first_monday = first_monday.next_occurring(:monday) unless first_monday.monday?
next_month_start = month_start.next_month.beginning_of_month # Trouver le premier lundi du mois SUIVANT
next_first_monday = next_month_start next_month_start = month_start.next_month.beginning_of_month
next_first_monday = next_first_monday.next_occurring(:monday) unless next_first_monday.monday? next_first_monday = next_month_start
next_first_monday = next_first_monday.next_occurring(:monday) unless next_first_monday.monday?
month_end = next_first_monday - 1.day # Le dernier jour du mois est le dimanche précédant le premier lundi du mois suivant
month_end = next_first_monday - 1.day
data_hash = complete_data.index_by { |d| d[:recorded_at].to_date } # 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 }
all_days = (first_monday..month_end).map do |date| # Générer tous les jours du premier lundi jusqu'au dimanche avant le prochain lundi
data_hash[date] || { mode: nil, recorded_at: date.to_datetime, guess: nil, day_log: nil } all_days = (first_monday..month_end).map do |date|
end # 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 }
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
# 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
end end

View file

@ -1,25 +0,0 @@
= form_with model: day_log do |f|
- @day_log.errors.full_messages.each do |msg|
.notification.is-danger= msg
.field
= f.label :info, "Journal", class: "label"
.control
= f.text_area :info, class: "textarea", rows: 5
.field
= label_tag :mode_id, "Mode", class: "label"
.control
.select.is-fullwidth
= select_tag "day_log[mode_id]",
options_from_collection_for_select(Current.user.modes, :id, :label, @mood&.mode_id),
include_blank: true
.field.is-hidden
= f.date_field :day
.field.is-grouped
.control
= f.submit "Enregistrer", class: "button is-primary"
.control
= link_to "Annuler", dashboard_path(day: @day_log.day), class: "button is-light"

View file

@ -1,13 +0,0 @@
%main.columns.m-auto
.current-day.column.is-hidden-mobile
%section.m-4
%figure.image.has-ratio
= image_tag @mood ? @mood.mode.image_url : "unknown.jpg"
%section.section.column.has-background-primary-light
%h1.title.is-4
%span.icon
%i.fa-regular.fa-calendar
%span= l @day_log.day, format: :long
= render 'form', day_log: @day_log

View file

@ -1,3 +0,0 @@
%h1 Nouveau journal
= render 'form', day_log: @day_log

View file

@ -2,7 +2,7 @@
<div class="current-day column is-hidden-mobile"> <div class="current-day column is-hidden-mobile">
<section class="m-4"> <section class="m-4">
<figure class="image has-ratio"> <figure class="image has-ratio">
<%= image_tag(@user.current_mode.image_url, "data-mood-target": "image") %> <%= image_tag(@user.current_mood + ".jpg", "data-mood-target": "image") %>
</figure> </figure>
</section> </section>
</div> </div>
@ -12,24 +12,33 @@
<div class="is-flex is-align-items-center mr-4"> <div class="is-flex is-align-items-center mr-4">
<div class=""><strong>Légende</strong></div> <div class=""><strong>Légende</strong></div>
</div> </div>
<% @user.modes.each do |mode| %> <div class="is-flex is-align-items-center mr-4">
<div class="is-flex is-align-items-center mr-4"> <div class="bar-frigo mr-1"></div>
<div class="bar mr-1" style="<%= style_for_mode(mode, :filled) %>"></div> <div class="">Triste</div>
<div class=""><%= mode.label %></div> </div>
</div> <div class="is-flex is-align-items-center mr-4">
<% end %> <div class="bar-en-charge mr-1"></div>
<div class="">En charge</div>
</div>
<div class="is-flex is-align-items-center mr-4">
<div class="bar-croisiere mr-1"></div>
<div class="">Croisiere</div>
</div>
<div class="is-flex is-align-items-center mr-4">
<div class="bar-creatif mr-1"></div>
<div>Creatif</div>
</div>
<div class="is-flex is-align-items-center mr-4">
<div class="bar-afond mr-1"></div>
<div class="">A fond</div>
</div>
</div> </div>
<div class="container mb-4 p-3 has-background-white day-info"> <div class="container mb-4 p-3 has-background-white day-info">
<div class="title is-4"> <div class="title is-4">
<span class="icon"><i class="fa-regular fa-calendar"></i></span> <span class="icon"><i class="fa-regular fa-calendar"></i></span>
<span data-mood-target="modeDay">Aujourd'hui : <%= @user.current_mode.label %></span> <span data-mood-target="modeDay">Aujourd'hui : <%= @user.current_mood %></span>
<%= link_to edit_day_log_for_day_path(day: Date.today), "data-mood-target": "dayLogLink" do %>
<span class="icon ml-2"><i class="has-text-grey fa-solid fa-pencil fa-xs"></i></span>
<% end %>
</div> </div>
<p data-mood-target="dayLogInfo"><%= @user.current_day_log&.info %></p>
</div> </div>
<div class="logs"> <div class="logs">
<div class="is-flex is-flex-direction-row is-flex-wrap-wrap mb-3"> <div class="is-flex is-flex-direction-row is-flex-wrap-wrap mb-3">
<% @user.history.each do |month| %> <% @user.history.each do |month| %>
@ -39,23 +48,15 @@
<% month[:weeks].each do |week| %> <% month[:weeks].each do |week| %>
<div class="is-flex is-flex-direction-column is-flex-wrap-wrap mb-3"> <div class="is-flex is-flex-direction-column is-flex-wrap-wrap mb-3">
<% week.each do |mood| %> <% week.each do |mood| %>
<% status = day_status(mood, @user) %>
<% mode = mode_for(mood, @user) %> <% mode = mode_for(mood, @user) %>
<div data-image="<%= mode.image_url %>" <div data-image="<%= asset_path(mode + ".jpg") %>" data-mode="<%= mode %>" data-day="<%= l mood[:recorded_at].to_date %>" data-action="click->mood#updateDayInfo" title="<%= l(mood[:recorded_at].to_date) %> : <%= mode %>" class="day <%= mode %>"></div>
data-mode="<%= mode.label %>"
data-day="<%= mood[:recorded_at].to_date.iso8601 %>"
data-info="<%= mood[:day_log]&.info %>"
data-action="click->mood#updateDayInfo"
title="<%= l(mood[:recorded_at].to_date) %> : <%= mode.label %>"
class="<%= css_class_for_day(status) %>"
style="<%= style_for_mode(mode, status) %>">
</div>
<% end %> <% end %>
</div> </div>
<% end %> <% end %>
</div> </div>
</div> </div>
<% end %> <% end %>
<div id="end"></>
</div> </div>
</div> </div>
</div> </div>

View file

@ -21,7 +21,4 @@ Rails.application.routes.draw do
patch "/invite/:token", to: "invitations#update", as: :invitation patch "/invite/:token", to: "invitations#update", as: :invitation
get "/moods", to: "moods#index", as: :dashboard get "/moods", to: "moods#index", as: :dashboard
get "/day_logs/edit", to: "day_logs#edit", as: :edit_day_log_for_day
resources :day_logs, only: [ :create, :update ]
end end

View file

@ -1,11 +0,0 @@
class CreateModes < ActiveRecord::Migration[8.0]
def change
create_table :modes do |t|
t.string :label
t.string :color
t.references :user, null: false, foreign_key: true
t.timestamps
end
end
end

View file

@ -1,34 +0,0 @@
class ReplaceMoodModeStringWithModeReference < ActiveRecord::Migration[8.0]
def up
rename_column :moods, :mode, :mode_string
# 1. Ajouter la colonne de référence
add_reference :moods, :mode, foreign_key: true
# 2. Pour chaque mood, trouver ou créer le Mode correspondant
Mood.find_each do |mood|
next if mood.mode_string.blank?
mode_record = Mode.find_or_create_by!(label: mood.mode_string, user_id: mood.user_id) do |m|
m.color = "#000000" # couleur par défaut
end
mood.update_column(:mode_id, mode_record.id)
end
# 3. Supprimer l'ancienne colonne string
remove_column :moods, :mode_string, :string
end
def down
add_column :moods, :mode_string, :string
Mood.find_each do |mood|
mood.update_column(:mode_string, mood.mode&.label)
end
remove_reference :moods, :mode, foreign_key: true
rename_column :moods, :mode_string, :mode
end
end

View file

@ -1,57 +0,0 @@
# This migration comes from active_storage (originally 20170806125915)
class CreateActiveStorageTables < ActiveRecord::Migration[7.0]
def change
# Use Active Record's configured type for primary and foreign keys
primary_key_type, foreign_key_type = primary_and_foreign_key_types
create_table :active_storage_blobs, id: primary_key_type do |t|
t.string :key, null: false
t.string :filename, null: false
t.string :content_type
t.text :metadata
t.string :service_name, null: false
t.bigint :byte_size, null: false
t.string :checksum
if connection.supports_datetime_with_precision?
t.datetime :created_at, precision: 6, null: false
else
t.datetime :created_at, null: false
end
t.index [ :key ], unique: true
end
create_table :active_storage_attachments, id: primary_key_type do |t|
t.string :name, null: false
t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
t.references :blob, null: false, type: foreign_key_type
if connection.supports_datetime_with_precision?
t.datetime :created_at, precision: 6, null: false
else
t.datetime :created_at, null: false
end
t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
create_table :active_storage_variant_records, id: primary_key_type do |t|
t.belongs_to :blob, null: false, index: false, type: foreign_key_type
t.string :variation_digest, null: false
t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
end
private
def primary_and_foreign_key_types
config = Rails.configuration.generators
setting = config.options[config.orm][:primary_key_type]
primary_key_type = setting || :primary_key
foreign_key_type = setting || :bigint
[ primary_key_type, foreign_key_type ]
end
end

View file

@ -1,13 +0,0 @@
class CreateDayLogs < ActiveRecord::Migration[8.0]
def change
create_table :day_logs do |t|
t.date :day, null: false
t.text :info, null: false
t.references :user, null: false, foreign_key: true
t.index :day, unique: true
t.timestamps
end
end
end

59
db/schema.rb generated
View file

@ -10,61 +10,13 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2026_03_19_181607) do ActiveRecord::Schema[8.0].define(version: 2026_02_26_101258) do
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
t.bigint "record_id", null: false
t.bigint "blob_id", null: false
t.datetime "created_at", null: false
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end
create_table "active_storage_blobs", force: :cascade do |t|
t.string "key", null: false
t.string "filename", null: false
t.string "content_type"
t.text "metadata"
t.string "service_name", null: false
t.bigint "byte_size", null: false
t.string "checksum"
t.datetime "created_at", null: false
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end
create_table "active_storage_variant_records", force: :cascade do |t|
t.bigint "blob_id", null: false
t.string "variation_digest", null: false
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
end
create_table "day_logs", force: :cascade do |t|
t.date "day", null: false
t.text "info", null: false
t.integer "user_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["day"], name: "index_day_logs_on_day", unique: true
t.index ["user_id"], name: "index_day_logs_on_user_id"
end
create_table "modes", force: :cascade do |t|
t.string "label"
t.string "color"
t.integer "user_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_modes_on_user_id"
end
create_table "moods", force: :cascade do |t| create_table "moods", force: :cascade do |t|
t.string "mode"
t.datetime "recorded_at" t.datetime "recorded_at"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.integer "user_id" t.integer "user_id"
t.integer "mode_id"
t.index ["mode_id"], name: "index_moods_on_mode_id"
t.index ["user_id"], name: "index_moods_on_user_id" t.index ["user_id"], name: "index_moods_on_user_id"
end end
@ -75,7 +27,7 @@ ActiveRecord::Schema[8.0].define(version: 2026_03_19_181607) do
t.datetime "updated_at", default: -> { "CURRENT_DATE" }, null: false t.datetime "updated_at", default: -> { "CURRENT_DATE" }, null: false
t.string "chip_id" t.string "chip_id"
t.integer "user_id" t.integer "user_id"
t.index ["identifier"], name: "index_rfid_tags_on_identifier", unique: true t.index ["identifier"], name: "index_rfid_tags_on_identifier"
t.index ["user_id"], name: "index_rfid_tags_on_user_id" t.index ["user_id"], name: "index_rfid_tags_on_user_id"
end end
@ -100,11 +52,6 @@ ActiveRecord::Schema[8.0].define(version: 2026_03_19_181607) do
t.index ["email_address"], name: "index_users_on_email_address", unique: true t.index ["email_address"], name: "index_users_on_email_address", unique: true
end end
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "day_logs", "users"
add_foreign_key "modes", "users"
add_foreign_key "moods", "modes"
add_foreign_key "moods", "users" add_foreign_key "moods", "users"
add_foreign_key "rfid_tags", "users" add_foreign_key "rfid_tags", "users"
add_foreign_key "sessions", "users" add_foreign_key "sessions", "users"

View file

@ -1,7 +0,0 @@
FactoryBot.define do
factory :day_log do
day { "2026-03-19" }
info { "Arrivée à Paris" }
user { association :user }
end
end

View file

@ -1,7 +0,0 @@
FactoryBot.define do
factory :mode do
label { "MyString" }
color { "MyString" }
user { nil }
end
end

View file

@ -1,7 +1,7 @@
FactoryBot.define do FactoryBot.define do
factory :mood do factory :mood do
recorded_at { DateTime.parse("2026-01-15 10:00:00") } mode { "croisiere" }
recorded_at { DateTime.now }
association :user association :user
association :mode
end end
end end

View file

@ -1,8 +1,6 @@
FactoryBot.define do FactoryBot.define do
factory :user do factory :user do
sequence(:email_address) { |n| "user#{n}@example.com" } sequence(:email_address) { |n| "user#{n}@example.com" }
sequence(:username) { |n| "user#{n}" } password_digest { BCrypt::Password.create('password123') }
password { "obanonalors" }
password_digest { BCrypt::Password.create(password) }
end end
end end

View file

@ -1,24 +0,0 @@
require 'rails_helper'
describe DayLog, type: :model do
subject { build(:day_log) }
it { is_expected.to be_valid }
describe "validations" do
it "is invalid without a day" do
subject.day = nil
expect(subject).not_to be_valid
end
it "is invalid without info" do
subject.info = nil
expect(subject).not_to be_valid
end
it "is invalid without a user" do
subject.user = nil
expect(subject).not_to be_valid
end
end
end

View file

@ -1,5 +0,0 @@
require 'rails_helper'
RSpec.describe Mode, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View file

@ -1,46 +1,7 @@
require 'rails_helper' require 'rails_helper'
describe Mood do describe Mood, type: :model do
let(:user) { create(:user) } it 'works' do
let(:mode) { create(:mode, user: user) } expect(true)
subject { build(:mood, user: user, mode: mode, recorded_at: DateTime.parse("2026-01-15 10:00:00")) }
it { is_expected.to be_valid }
describe "validations" do
it "is invalid without a user" do
subject.user = nil
expect(subject).not_to be_valid
end
it "is invalid without a mode" do
subject.mode = nil
expect(subject).not_to be_valid
end
it "is invalid without a recorded_at" do
subject.recorded_at = nil
expect(subject).not_to be_valid
end
end
describe "uniqueness per day and user" do
it "is invalid if a mood already exists for the same day and user" do
create(:mood, user: user, mode: mode, recorded_at: DateTime.parse("2026-01-15 18:00:00"))
expect(subject).not_to be_valid
end
it "is valid if the same day exists but for a different user" do
other_user = create(:user)
other_mode = create(:mode, user: other_user)
create(:mood, user: other_user, mode: other_mode, recorded_at: DateTime.parse("2026-01-15 10:00:00"))
expect(subject).to be_valid
end
it "is valid if the same user has a mood on a different day" do
create(:mood, user: user, mode: mode, recorded_at: DateTime.parse("2026-01-14 10:00:00"))
expect(subject).to be_valid
end
end end
end end

View file

@ -1,251 +0,0 @@
require 'rails_helper'
describe "DayLogs", type: :request do
let(:user) { create(:user) }
let(:mode) { create(:mode, user: user) }
let(:day) { Date.parse("2026-01-15") }
describe "GET /day_logs/edit" do
context "when not authenticated" do
it "redirects to the login page" do
get edit_day_log_for_day_path(day: day)
expect(response).to redirect_to(new_session_path)
end
end
context "when authenticated" do
before { login_as(user) }
context "with an existing day_log for the day" do
let!(:day_log) { create(:day_log, user: user, day: day) }
it "returns http success" do
get edit_day_log_for_day_path(day: day)
expect(response).to have_http_status(:success)
end
end
context "without an existing day_log for the day" do
it "returns http success" do
get edit_day_log_for_day_path(day: day)
expect(response).to have_http_status(:success)
end
it "does not persist a day_log" do
expect {
get edit_day_log_for_day_path(day: day)
}.not_to change(DayLog, :count)
end
end
end
end
describe "POST /day_logs" do
context "when not authenticated" do
it "redirects to the login page" do
post day_logs_path, params: { day_log: { day: day, info: "Une info" } }
expect(response).to redirect_to(new_session_path)
end
end
context "when authenticated" do
before { login_as(user) }
context "with valid params" do
context "without mode" do
it "creates a day_log" do
expect {
post day_logs_path, params: { day_log: { day: day, info: "Une info" } }
}.to change(DayLog, :count).by(1)
end
it "does not create a mood" do
expect {
post day_logs_path, params: { day_log: { day: day, info: "Une info" } }
}.not_to change(Mood, :count)
end
end
context "with mode, no existing mood" do
it "creates a day_log" do
expect {
post day_logs_path, params: { day_log: { day: day, info: "Une info", mode_id: mode.id } }
}.to change(DayLog, :count).by(1)
end
it "creates a mood" do
expect {
post day_logs_path, params: { day_log: { day: day, info: "Une info", mode_id: mode.id } }
}.to change(Mood, :count).by(1)
end
end
context "with mode, existing mood" do
let!(:existing_mood) { create(:mood, user: user, mode: mode, recorded_at: DateTime.parse("2026-01-15 08:00:00")) }
let(:other_mode) { create(:mode, user: user) }
it "creates a day_log" do
expect {
post day_logs_path, params: { day_log: { day: day, info: "Une info", mode_id: other_mode.id } }
}.to change(DayLog, :count).by(1)
end
it "does not create a new mood" do
expect {
post day_logs_path, params: { day_log: { day: day, info: "Une info", mode_id: other_mode.id } }
}.not_to change(Mood, :count)
end
it "updates the existing mood mode" do
post day_logs_path, params: { day_log: { day: day, info: "Une info", mode_id: other_mode.id } }
expect(existing_mood.reload.mode).to eq(other_mode)
end
end
it "redirects after creation" do
post day_logs_path, params: { day_log: { day: day, info: "Une info" } }
expect(response).to have_http_status(:redirect)
end
end
context "with invalid params" do
it "does not create a day_log" do
expect {
post day_logs_path, params: { day_log: { day: nil, info: nil } }
}.not_to change(DayLog, :count)
end
it "returns unprocessable content" do
post day_logs_path, params: { day_log: { day: nil, info: nil } }
expect(response).to have_http_status(:unprocessable_content)
end
end
end
end
describe "GET /day_logs/:id/edit" do
let!(:day_log) { create(:day_log, user: user, day: day) }
context "when not authenticated" do
it "redirects to the login page" do
get edit_day_log_path(day_log)
expect(response).to redirect_to(new_session_path)
end
end
context "when authenticated" do
before { login_as(user) }
it "returns http success" do
get edit_day_log_path(day_log)
expect(response).to have_http_status(:success)
end
end
end
describe "GET /day_logs/edit" do
context "when not authenticated" do
it "redirects to the login page" do
get edit_day_log_for_day_path(day: day)
expect(response).to redirect_to(new_session_path)
end
end
context "when authenticated" do
before { login_as(user) }
context "with an existing day_log for the day" do
let!(:day_log) { create(:day_log, user: user, day: day) }
it "returns http success" do
get edit_day_log_for_day_path(day: day)
expect(response).to have_http_status(:success)
end
end
context "without an existing day_log for the day" do
it "returns http success" do
get edit_day_log_for_day_path(day: day)
expect(response).to have_http_status(:success)
end
it "does not persist a day_log" do
expect {
get edit_day_log_for_day_path(day: day)
}.not_to change(DayLog, :count)
end
end
end
end
describe "PATCH /day_logs/:id" do
let!(:day_log) { create(:day_log, user: user, day: day) }
context "when not authenticated" do
it "redirects to the login page" do
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info" } }
expect(response).to redirect_to(new_session_path)
end
end
context "when authenticated" do
before { login_as(user) }
context "with valid params" do
context "without mode" do
it "updates the day_log" do
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info" } }
expect(day_log.reload.info).to eq("Nouvelle info")
end
it "does not create a mood" do
expect {
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info" } }
}.not_to change(Mood, :count)
end
it "redirects to dashboard" do
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info" } }
expect(response).to redirect_to(dashboard_path)
end
end
context "with mode, no existing mood" do
it "creates a mood" do
expect {
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info", mode_id: mode.id } }
}.to change(Mood, :count).by(1)
end
end
context "with mode, existing mood" do
let!(:existing_mood) { create(:mood, user: user, mode: mode, recorded_at: DateTime.parse("2026-01-15 08:00:00")) }
let(:other_mode) { create(:mode, user: user) }
it "does not create a new mood" do
expect {
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info", mode_id: other_mode.id } }
}.not_to change(Mood, :count)
end
it "updates the existing mood mode" do
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info", mode_id: other_mode.id } }
expect(existing_mood.reload.mode).to eq(other_mode)
end
end
end
context "with invalid params" do
it "does not update the day_log" do
patch day_log_path(day_log), params: { day_log: { info: nil } }
expect(day_log.reload.info).not_to be_nil
end
it "returns unprocessable content" do
patch day_log_path(day_log), params: { day_log: { info: nil } }
expect(response).to have_http_status(:unprocessable_content)
end
end
end
end
end

View file

@ -1,231 +1,330 @@
# spec/services/mood_calendar_service_spec.rb
require 'rails_helper' require 'rails_helper'
RSpec.describe MoodCalendarService do RSpec.describe MoodCalendarService do
let(:user) { create(:user) }
let(:mode) { create(:mode, user: user) }
describe '.generate_calendar' do describe '.generate_calendar' do
context 'avec des jours manquants' do context 'avec un recorded_at pour chaque jour' do
it 'remplit les jours manquants avec mode: nil et guess égal au dernier mode' do it 'génère un calendrier complet sans jours nil' do
mode2 = create(:mode, user: user) # Données du 1er au 7 février 2025 (une semaine complète)
mode3 = create(:mode, user: user) (1..7).each do |day|
create(:mood, user: user, mode: mode2, recorded_at: Time.zone.parse("2025-02-12 14:20:00")) create(:mood, mode: "mood_#{day}", recorded_at: Time.zone.parse("2025-02-0#{day} 10:00:00"))
create(:mood, user: user, mode: mode3, recorded_at: Time.zone.parse("2025-02-15 10:00:00")) end
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-18 16:45:00"))
result = MoodCalendarService.generate_calendar( result = MoodCalendarService.generate_calendar(
user, Mood.all,
end_date: Date.parse("2025-02-28") end_date: Date.parse("2025-02-28")
) )
expect(result.length).to be >= 1 expect(result.length).to eq(1) # Un seul mois
expect(result.any? { |m| m[:month] == Date.parse("2025-02-01") }).to be true expect(result[0][:month]).to eq(Date.parse("2025-02-01"))
all_days = result.find { |m| m[:month] == Date.parse("2025-02-01") }[:weeks].flatten # Première semaine commence le lundi 3 février
first_week = result[0][:weeks][0]
expect(first_week.length).to eq(7) # Du lundi 3 au dimanche 9
# Vérifier que tous les jours du 3 au 7 ont un mode
(3..7).each do |day|
mood = first_week.find { |m| m[:recorded_at].day == day }
expect(mood[:mode]).to eq("mood_#{day}")
expect(mood).not_to have_key(:guess)
end
# Le 8 et 9 février devraient avoir mode: nil avec guess: "mood_7"
mood_8 = first_week.find { |m| m[:recorded_at].day == 8 }
expect(mood_8[:mode]).to be_nil
expect(mood_8[:guess]).to eq("mood_7")
mood_9 = first_week.find { |m| m[:recorded_at].day == 9 }
expect(mood_9[:mode]).to be_nil
expect(mood_9[:guess]).to eq("mood_7")
end
end
context 'avec des jours manquants' do
it 'remplit les jours manquants avec mode: nil et guess égal au dernier mode' do
create(:mood, mode: "triste", recorded_at: Time.zone.parse("2025-02-12 08:30:00"))
create(:mood, mode: "content", recorded_at: Time.zone.parse("2025-02-12 14:20:00"))
create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, mode: "calme", recorded_at: Time.zone.parse("2025-02-18 16:45:00"))
moods = Mood.all
result = MoodCalendarService.generate_calendar(
moods,
end_date: Date.parse("2025-02-28")
)
expect(result.length).to eq(1)
expect(result[0][:month]).to eq(Date.parse("2025-02-01"))
# Trouver tous les jours
all_days = result[0][:weeks].flatten
# Le 12 février devrait avoir le dernier mood (content à 14:20)
mood_12 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-12") } mood_12 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-12") }
expect(mood_12[:mode]).to eq(mode2) expect(mood_12[:mode]).to eq("content")
expect(mood_12[:recorded_at].to_i).to eq(Time.zone.parse("2025-02-12 14:20:00").to_i) expect(mood_12[:recorded_at].to_i).to eq(Time.zone.parse("2025-02-12 14:20:00").to_i)
# Le 13 février devrait être nil avec guess: "content"
mood_13 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-13") } mood_13 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-13") }
expect(mood_13[:mode]).to be_nil expect(mood_13[:mode]).to be_nil
expect(mood_13[:guess]).to eq(mode2) expect(mood_13[:guess]).to eq("content")
# Le 14 février devrait être nil avec guess: "content"
mood_14 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-14") } mood_14 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-14") }
expect(mood_14[:mode]).to be_nil expect(mood_14[:mode]).to be_nil
expect(mood_14[:guess]).to eq(mode2) expect(mood_14[:guess]).to eq("content")
# Le 15 février devrait avoir le mood "heureux"
mood_15 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-15") } mood_15 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-15") }
expect(mood_15[:mode]).to eq(mode3) expect(mood_15[:mode]).to eq("heureux")
# Le 16 février devrait être nil avec guess: "heureux"
mood_16 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-16") } mood_16 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-16") }
expect(mood_16[:mode]).to be_nil expect(mood_16[:mode]).to be_nil
expect(mood_16[:guess]).to eq(mode3) expect(mood_16[:guess]).to eq("heureux")
# Les jours avant le 12 (à partir du premier lundi 3 février) devraient avoir guess: nil
mood_3 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-03") } mood_3 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-03") }
expect(mood_3[:mode]).to be_nil expect(mood_3[:mode]).to be_nil
expect(mood_3[:guess]).to be_nil expect(mood_3[:guess]).to be_nil
end end
it 'gère correctement plusieurs mois avec des jours manquants' do it 'gère correctement plusieurs mois avec des jours manquants' do
mode2 = create(:mode, user: user) create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00")) create(:mood, mode: "joyeux", recorded_at: Time.zone.parse("2025-04-20 11:30:00"))
create(:mood, user: user, mode: mode2, recorded_at: Time.zone.parse("2025-04-20 11:30:00"))
result = MoodCalendarService.generate_calendar( result = MoodCalendarService.generate_calendar(
user, Mood.all,
end_date: Date.parse("2025-04-30") end_date: Date.parse("2025-04-30")
) )
expect(result.length).to be >= 3 # Devrait avoir exactement février, mars et avril
expect(result.any? { |m| m[:month] == Date.parse("2025-02-01") }).to be true expect(result.length).to eq(3)
expect(result.any? { |m| m[:month] == Date.parse("2025-03-01") }).to be true
expect(result.any? { |m| m[:month] == Date.parse("2025-04-01") }).to be true
# Vérifier février
february = result.find { |m| m[:month] == Date.parse("2025-02-01") }
expect(february).not_to be_nil
# Vérifier mars (tous les jours devraient avoir mode: nil et guess: "heureux")
march = result.find { |m| m[:month] == Date.parse("2025-03-01") } march = result.find { |m| m[:month] == Date.parse("2025-03-01") }
expect(march).not_to be_nil
all_march_days = march[:weeks].flatten all_march_days = march[:weeks].flatten
# Tous les jours de mars (y compris ceux qui débordent début avril) devraient avoir mode: nil
expect(all_march_days.all? { |d| d[:mode].nil? }).to be true expect(all_march_days.all? { |d| d[:mode].nil? }).to be true
# Tous les jours de mars devraient avoir guess: "heureux"
# (le mois de mars va du 3 mars au 6 avril, avant le mood du 20 avril)
all_march_days.each do |day| all_march_days.each do |day|
expect(day[:guess]).to be_present, expect(day[:guess]).to eq("heureux"),
"Le jour #{day[:recorded_at].to_date} devrait avoir un guess" "Le jour #{day[:recorded_at].to_date} devrait avoir guess: 'heureux' mais a: #{day[:guess].inspect}"
end end
# Vérifier avril
april = result.find { |m| m[:month] == Date.parse("2025-04-01") } april = result.find { |m| m[:month] == Date.parse("2025-04-01") }
expect(april).not_to be_nil
all_april_days = april[:weeks].flatten all_april_days = april[:weeks].flatten
mood_20_april = all_april_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-04-20") } mood_20_april = all_april_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-04-20") }
expect(mood_20_april[:mode]).to eq(mode2) expect(mood_20_april[:mode]).to eq("joyeux")
# Les jours du 7 au 19 avril devraient avoir guess: "heureux"
mood_10_april = all_april_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-04-10") } mood_10_april = all_april_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-04-10") }
expect(mood_10_april[:guess]).to eq(mode) expect(mood_10_april[:guess]).to eq("heureux")
# Les jours après le 20 avril devraient avoir guess: "joyeux"
mood_21_april = all_april_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-04-21") } mood_21_april = all_april_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-04-21") }
expect(mood_21_april[:guess]).to eq(mode2) expect(mood_21_april[:guess]).to eq("joyeux")
end end
end end
context 'sans aucun recorded_at' do context 'sans aucun recorded_at' do
it 'retourne un calendrier à partir de la date courante' do it 'retourne un tableau vide' do
result = MoodCalendarService.generate_calendar(user) result = MoodCalendarService.generate_calendar(Mood.none)
expect(result).not_to be_empty expect(result).to eq([])
expect(result.first[:month]).to eq(Date.current.beginning_of_month) end
end
context 'avec plusieurs moods le même jour' do
it 'garde uniquement le mood le plus récent' do
create(:mood, mode: "triste", recorded_at: Time.zone.parse("2025-02-12 08:30:00"))
create(:mood, mode: "neutre", recorded_at: Time.zone.parse("2025-02-12 12:00:00"))
create(:mood, mode: "content", recorded_at: Time.zone.parse("2025-02-12 14:20:00"))
create(:mood, mode: "fatigué", recorded_at: Time.zone.parse("2025-02-12 09:15:00"))
moods = Mood.all
result = MoodCalendarService.generate_calendar(
moods,
end_date: Date.parse("2025-02-28")
)
all_days = result[0][:weeks].flatten
mood_12 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-12") }
expect(mood_12[:mode]).to eq("content")
expect(mood_12[:recorded_at].to_i).to eq(Time.zone.parse("2025-02-12 14:20:00").to_i)
end end
end end
context 'structure du calendrier' do context 'structure du calendrier' do
it 'commence chaque mois par le premier lundi' do it 'commence chaque mois par le premier lundi' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00")) create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar( result = MoodCalendarService.generate_calendar(
user, Mood.all,
end_date: Date.parse("2025-02-28") end_date: Date.parse("2025-02-28")
) )
february = result[0] february = result[0]
first_day = february[:weeks][0][0] first_day = february[:weeks][0][0]
# Le premier jour de février 2025 devrait être le lundi 3 février
expect(first_day[:recorded_at].to_date).to eq(Date.parse("2025-02-03")) expect(first_day[:recorded_at].to_date).to eq(Date.parse("2025-02-03"))
expect(first_day[:recorded_at].to_date.monday?).to be true expect(first_day[:recorded_at].to_date.monday?).to be true
end end
it 'va du premier lundi du mois au dimanche avant le premier lundi du mois suivant' do it 'va du premier lundi du mois au dimanche avant le premier lundi du mois suivant' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00")) create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar( result = MoodCalendarService.generate_calendar(
user, Mood.all,
end_date: Date.parse("2025-03-31") end_date: Date.parse("2025-03-31")
) )
february = result[0] february = result[0]
all_days = february[:weeks].flatten all_days = february[:weeks].flatten
# Premier jour : lundi 3 février 2025
first_day = all_days.first first_day = all_days.first
expect(first_day[:recorded_at].to_date).to eq(Date.parse("2025-02-03")) expect(first_day[:recorded_at].to_date).to eq(Date.parse("2025-02-03"))
expect(first_day[:recorded_at].to_date.monday?).to be true expect(first_day[:recorded_at].to_date.monday?).to be true
# Dernier jour : dimanche 2 mars 2025 (veille du premier lundi de mars qui est le 3)
last_day = all_days.last last_day = all_days.last
expect(last_day[:recorded_at].to_date).to eq(Date.parse("2025-03-02")) expect(last_day[:recorded_at].to_date).to eq(Date.parse("2025-03-02"))
expect(last_day[:recorded_at].to_date.sunday?).to be true expect(last_day[:recorded_at].to_date.sunday?).to be true
# Le 3 mars (premier lundi de mars) ne devrait PAS être dans février
expect(all_days.any? { |d| d[:recorded_at].to_date == Date.parse("2025-03-03") }).to be false expect(all_days.any? { |d| d[:recorded_at].to_date == Date.parse("2025-03-03") }).to be false
end end
it 'toutes les semaines ont exactement 7 jours' do it 'toutes les semaines ont exactement 7 jours' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00")) create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar( result = MoodCalendarService.generate_calendar(
user, Mood.all,
end_date: Date.parse("2025-02-28") end_date: Date.parse("2025-02-28")
) )
february = result[0] february = result[0]
# Toutes les semaines devraient avoir exactement 7 jours
february[:weeks].each do |week| february[:weeks].each do |week|
expect(week.length).to eq(7) expect(week.length).to eq(7)
end end
end end
it 'retourne month comme Date (le premier du mois)' do it 'retourne month comme Date (le premier du mois)' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00")) create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar( result = MoodCalendarService.generate_calendar(
user, Mood.all,
end_date: Date.parse("2025-02-28") end_date: Date.parse("2025-02-28")
) )
february = result[0] february = result[0]
# month devrait être une Date
expect(february[:month]).to be_a(Date) expect(february[:month]).to be_a(Date)
expect(february[:month]).to eq(Date.parse("2025-02-01")) expect(february[:month]).to eq(Date.parse("2025-02-01"))
expect(february[:month].day).to eq(1) expect(february[:month].day).to eq(1) # Premier du mois
end end
end end
context 'avec paramètres par défaut' do context 'avec paramètres par défaut' do
it "utilise le premier mood comme start_date et aujourd'hui comme end_date" do it 'utilise le premier mood comme start_date et aujourd\'hui comme end_date' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00")) create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar(user) result = MoodCalendarService.generate_calendar(Mood.all)
expect(result).not_to be_empty expect(result).not_to be_empty
# Devrait commencer en février
expect(result.first[:month]).to eq(Date.parse("2025-02-01")) expect(result.first[:month]).to eq(Date.parse("2025-02-01"))
end end
end end
context 'avec start_date spécifié' do context 'avec start_date spécifié' do
it 'commence à la date spécifiée' do it 'commence à la date spécifiée' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00")) create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar( result = MoodCalendarService.generate_calendar(
user, Mood.all,
start_date: Date.parse("2025-03-01"), start_date: Date.parse("2025-03-01"),
end_date: Date.parse("2025-03-31") end_date: Date.parse("2025-03-31")
) )
# Devrait commencer en mars même si le mood est en février
expect(result.first[:month]).to eq(Date.parse("2025-03-01")) expect(result.first[:month]).to eq(Date.parse("2025-03-01"))
# Ne devrait pas contenir février
expect(result.any? { |m| m[:month] == Date.parse("2025-02-01") }).to be false expect(result.any? { |m| m[:month] == Date.parse("2025-02-01") }).to be false
# Tous les jours de mars devraient avoir guess: "heureux" (du 15 février)
all_march_days = result.first[:weeks].flatten
puts all_march_days.inspect
expect(all_march_days.all? { |d| d[:guess] == "heureux" }).to be true
end end
end end
context 'avec end_date spécifié' do context 'avec end_date spécifié' do
it 'se termine à end_date' do it 'se termine à la date spécifiée' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00")) create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar( result = MoodCalendarService.generate_calendar(
user, Mood.all,
end_date: Date.parse("2025-09-30") end_date: Date.parse("2025-02-28")
) )
expect(result.last[:month]).to eq(Date.parse("2025-09-01")) # Devrait s'arrêter en février
expect(result.last[:month]).to eq(Date.parse("2025-02-01"))
# Ne devrait pas contenir mars ou au-delà
expect(result.any? { |m| m[:month] == Date.parse("2025-03-01") }).to be false
end end
end end
context 'avec start_date et end_date spécifiés' do context 'avec start_date et end_date spécifiés' do
it 'génère le calendrier pour la plage spécifiée' do it 'génère le calendrier pour la plage spécifiée' do
mode2 = create(:mode, user: user) create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00")) create(:mood, mode: "calme", recorded_at: Time.zone.parse("2025-04-20 10:00:00"))
create(:mood, user: user, mode: mode2, recorded_at: Time.zone.parse("2025-04-20 10:00:00"))
result = MoodCalendarService.generate_calendar( result = MoodCalendarService.generate_calendar(
user, Mood.all,
start_date: Date.parse("2025-03-01"), start_date: Date.parse("2025-03-01"),
end_date: Date.parse("2025-03-31") end_date: Date.parse("2025-03-31")
) )
# Devrait contenir uniquement mars
expect(result.length).to eq(1) expect(result.length).to eq(1)
expect(result.first[:month]).to eq(Date.parse("2025-03-01")) expect(result.first[:month]).to eq(Date.parse("2025-03-01"))
# Tous les jours de mars devraient avoir guess: "heureux" (du 15 février)
all_days = result.first[:weeks].flatten all_days = result.first[:weeks].flatten
expect(all_days.all? { |d| d[:mode].nil? }).to be true expect(all_days.all? { |d| d[:mode].nil? }).to be true
expect(all_days.all? { |d| d[:guess] == "heureux" }).to be true
end end
it 'gère une plage de plusieurs mois' do it 'gère une plage de plusieurs mois' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00")) create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar( result = MoodCalendarService.generate_calendar(
user, Mood.all,
start_date: Date.parse("2025-02-01"), start_date: Date.parse("2025-02-01"),
end_date: Date.parse("2025-04-30") end_date: Date.parse("2025-04-30")
) )
# Devrait contenir février, mars et avril
months = result.map { |m| m[:month] } months = result.map { |m| m[:month] }
expect(months).to include( expect(months).to include(
Date.parse("2025-02-01"), Date.parse("2025-02-01"),
@ -235,10 +334,11 @@ RSpec.describe MoodCalendarService do
end end
it 'fonctionne avec DateTime en paramètres' do it 'fonctionne avec DateTime en paramètres' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00")) create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
# Passer des DateTime au lieu de Date
result = MoodCalendarService.generate_calendar( result = MoodCalendarService.generate_calendar(
user, Mood.all,
start_date: Time.zone.parse("2025-02-01 00:00:00"), start_date: Time.zone.parse("2025-02-01 00:00:00"),
end_date: Time.zone.parse("2025-02-28 23:59:59") end_date: Time.zone.parse("2025-02-28 23:59:59")
) )
@ -250,109 +350,72 @@ RSpec.describe MoodCalendarService do
context 'avec une plage ne contenant aucun mood' do context 'avec une plage ne contenant aucun mood' do
it 'génère un calendrier avec tous les jours à nil et guess du dernier mood avant la plage' do it 'génère un calendrier avec tous les jours à nil et guess du dernier mood avant la plage' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00")) create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar( result = MoodCalendarService.generate_calendar(
user, Mood.all,
start_date: Date.parse("2025-04-01"), start_date: Date.parse("2025-04-01"),
end_date: Date.parse("2025-04-30") end_date: Date.parse("2025-04-30")
) )
# Devrait contenir avril
expect(result.first[:month]).to eq(Date.parse("2025-04-01")) expect(result.first[:month]).to eq(Date.parse("2025-04-01"))
# Tous les jours devraient avoir mode: nil et guess: "heureux"
all_days = result.first[:weeks].flatten all_days = result.first[:weeks].flatten
expect(all_days.all? { |d| d[:mode].nil? }).to be true expect(all_days.all? { |d| d[:mode].nil? }).to be true
expect(all_days.all? { |d| d[:guess] == "heureux" }).to be true
end end
end end
context 'avec plusieurs mois' do context 'avec plusieurs mois' do
it 'chaque mois commence et finit correctement' do it 'chaque mois commence et finit correctement' do
mode2 = create(:mode, user: user) create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00")) create(:mood, mode: "calme", recorded_at: Time.zone.parse("2025-03-20 10:00:00"))
create(:mood, user: user, mode: mode2, recorded_at: Time.zone.parse("2025-03-20 10:00:00"))
result = MoodCalendarService.generate_calendar( result = MoodCalendarService.generate_calendar(
user, Mood.all,
end_date: Date.parse("2025-03-31") end_date: Date.parse("2025-03-31")
) )
february = result.find { |m| m[:month] == Date.parse("2025-02-01") } february = result.find { |m| m[:month] == Date.parse("2025-02-01") }
march = result.find { |m| m[:month] == Date.parse("2025-03-01") } march = result.find { |m| m[:month] == Date.parse("2025-03-01") }
# Février se termine le dimanche 2 mars
last_day_february = february[:weeks].flatten.last last_day_february = february[:weeks].flatten.last
expect(last_day_february[:recorded_at].to_date).to eq(Date.parse("2025-03-02")) expect(last_day_february[:recorded_at].to_date).to eq(Date.parse("2025-03-02"))
# Mars commence le lundi 3 mars
first_day_march = march[:weeks].flatten.first first_day_march = march[:weeks].flatten.first
expect(first_day_march[:recorded_at].to_date).to eq(Date.parse("2025-03-03")) expect(first_day_march[:recorded_at].to_date).to eq(Date.parse("2025-03-03"))
# Pas de chevauchement
expect(last_day_february[:recorded_at].to_date + 1.day).to eq(first_day_march[:recorded_at].to_date) expect(last_day_february[:recorded_at].to_date + 1.day).to eq(first_day_march[:recorded_at].to_date)
end end
end end
context 'avec un scope spécifique' do context 'avec un scope spécifique' do
it 'fonctionne avec des moods filtrés par user' do it 'fonctionne avec des moods filtrés par user' do
user1 = create(:user)
user2 = create(:user) user2 = create(:user)
mode2 = create(:mode, user: user2)
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
create(:mood, user: user2, mode: mode2, recorded_at: Time.zone.parse("2025-02-16 10:00:00"))
create(:mood, mode: "heureux", recorded_at: Time.zone.parse("2025-02-15 10:00:00"), user: user1)
create(:mood, mode: "triste", recorded_at: Time.zone.parse("2025-02-16 10:00:00"), user: user2)
moods = Mood.where(user: user1)
result = MoodCalendarService.generate_calendar( result = MoodCalendarService.generate_calendar(
user, moods,
end_date: Date.parse("2025-02-28") end_date: Date.parse("2025-02-28")
) )
all_days = result[0][:weeks].flatten all_days = result[0][:weeks].flatten
mood_15 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-15") } mood_15 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-15") }
expect(mood_15[:mode]).to eq(mode) expect(mood_15[:mode]).to eq("heureux")
# Le mood du user2 ne devrait pas apparaître
mood_16 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-16") } mood_16 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-16") }
expect(mood_16[:mode]).to be_nil expect(mood_16[:mode]).to be_nil
expect(mood_16[:guess]).to eq(mode) expect(mood_16[:guess]).to eq("heureux")
end
end
context 'avec day_log' do
it 'inclut le day_log dans les données du jour correspondant' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
day_log = create(:day_log, user: user, day: Date.parse("2025-02-15"), info: "Une belle journée")
result = MoodCalendarService.generate_calendar(user, end_date: Date.parse("2025-02-28"))
all_days = result.find { |m| m[:month] == Date.parse("2025-02-01") }[:weeks].flatten
day_15 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-15") }
expect(day_15[:day_log]).to eq(day_log)
end
it 'retourne nil pour day_log quand il n\'en existe pas pour ce jour' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
result = MoodCalendarService.generate_calendar(user, end_date: Date.parse("2025-02-28"))
all_days = result.find { |m| m[:month] == Date.parse("2025-02-01") }[:weeks].flatten
day_15 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-15") }
expect(day_15[:day_log]).to be_nil
end
it 'inclut le day_log pour un jour sans mood' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
day_log = create(:day_log, user: user, day: Date.parse("2025-02-16"), info: "Jour sans mood")
result = MoodCalendarService.generate_calendar(user, end_date: Date.parse("2025-02-28"))
all_days = result.find { |m| m[:month] == Date.parse("2025-02-01") }[:weeks].flatten
day_16 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-16") }
expect(day_16[:mode]).to be_nil
expect(day_16[:day_log]).to eq(day_log)
end
it 'inclut le day_log pour un jour avec mood' do
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
day_log = create(:day_log, user: user, day: Date.parse("2025-02-15"), info: "Jour avec mood")
result = MoodCalendarService.generate_calendar(user, end_date: Date.parse("2025-02-28"))
all_days = result.find { |m| m[:month] == Date.parse("2025-02-01") }[:weeks].flatten
day_15 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-15") }
expect(day_15[:mode]).to eq(mode)
expect(day_15[:day_log]).to eq(day_log)
end end
end end
end end

View file

@ -1,12 +0,0 @@
module AuthenticationHelper
def login_as(user)
post session_path, params: {
email_address: user.email_address,
password: user.password
}
end
end
RSpec.configure do |config|
config.include AuthenticationHelper, type: :request
end