feat: for gestionnaire highlight card and commentaires if any unread

This commit is contained in:
seb-by-ouidou 2023-11-02 11:23:21 +00:00 committed by seb-by-ouidou
parent a5d95b2d9d
commit 6cd8b6d2df
22 changed files with 235 additions and 15 deletions

View file

@ -0,0 +1,12 @@
.fr-groupe_gestionnaire_cards {
.fr-h6 {
padding-left: 20px;
padding-right: 20px;
position: relative;
}
.notifications {
top: 3px;
right: 3px;
}
}

View file

@ -1,8 +1,9 @@
class GroupeGestionnaire::Card::CommentairesComponent < ApplicationComponent
def initialize(groupe_gestionnaire:, administrateur:, path:)
def initialize(groupe_gestionnaire:, administrateur:, path:, unread_commentaires: nil)
@groupe_gestionnaire = groupe_gestionnaire
@administrateur = administrateur
@path = path
@unread_commentaires = unread_commentaires
end
def number_commentaires

View file

@ -1,10 +1,12 @@
.fr-col-6.fr-col-md-4.fr-col-lg-3
= link_to @path, id: 'administrateurs', class: 'fr-tile fr-enlarge-link' do
= link_to @path, id: 'commentaires', class: 'fr-tile fr-enlarge-link' do
.fr-tile__body.flex.column.align-center.justify-between
%p.fr-badge.fr-badge--success Validé
%div
.line-count.fr-my-1w
%p.fr-tag= number_commentaires
%h3.fr-h6
- if @unread_commentaires
%span.notifications{ 'aria-label': 'notifications' }
= t('.title', count: number_commentaires)
%p.fr-btn.fr-btn--tertiary= t('views.shared.actions.see')

View file

@ -1,15 +1,28 @@
class GroupeGestionnaire::GroupeGestionnaireCommentaires::CommentaireComponent < ApplicationComponent
include ApplicationHelper
def initialize(commentaire:, connected_user:, is_gestionnaire: true)
def initialize(commentaire:, connected_user:, commentaire_seen_at: nil, is_gestionnaire: true)
@commentaire = commentaire
@connected_user = connected_user
@is_gestionnaire = is_gestionnaire
@groupe_gestionnaire = commentaire.groupe_gestionnaire
@commentaire_seen_at = commentaire_seen_at
end
private
def highlight_if_unseen_class
if highlight?
'highlighted'
end
end
def scroll_to_target
if highlight?
{ scroll_to_target: 'to' }
end
end
def commentaire_issuer
if @commentaire.sent_by?(@connected_user)
t('.you')
@ -22,4 +35,8 @@ class GroupeGestionnaire::GroupeGestionnaireCommentaires::CommentaireComponent <
is_current_year = (@commentaire.created_at.year == Time.zone.today.year)
l(@commentaire.created_at, format: is_current_year ? :message_date : :message_date_with_year)
end
def highlight?
@commentaire.persisted? && (@commentaire_seen_at.nil? || @commentaire_seen_at < @commentaire.created_at)
end
end

View file

@ -3,7 +3,7 @@
%span.mail
= commentaire_issuer
%span.date{ class: ["fr-text--xs", "fr-text-mention--grey", "font-weight-normal"] }
%span.date{ class: ["fr-text--xs", "fr-text-mention--grey", "font-weight-normal", highlight_if_unseen_class], data: scroll_to_target }
= commentaire_date
.rich-text
- if @commentaire.discarded?

View file

@ -23,4 +23,9 @@ class GroupeGestionnaire::GroupeGestionnaireListCommentaires::CommentaireCompone
gestionnaire_groupe_gestionnaire_commentaire_path(@groupe_gestionnaire, @commentaire),
class: 'button'
end
def highlight?
commentaire_seen_at = current_gestionnaire.commentaire_seen_at(@groupe_gestionnaire, @commentaire.sender)
commentaire_seen_at.nil? || commentaire_seen_at < @commentaire.created_at
end
end

View file

@ -1,4 +1,7 @@
%tr{ id: dom_id(@commentaire) }
%td= email
%td
= email
- if highlight?
%span.notifications{ 'aria-label': 'notifications' }
%td= created_at
%td= see_button

View file

@ -7,7 +7,9 @@ module Gestionnaires
end
def show
@commentaire_seen_at = current_gestionnaire.commentaire_seen_at(@groupe_gestionnaire, @last_commentaire.sender)
@commentaire = CommentaireGroupeGestionnaire.new
current_gestionnaire.mark_commentaire_as_seen(@groupe_gestionnaire, @last_commentaire.sender)
end
def create
@ -30,7 +32,6 @@ module Gestionnaires
else
flash.alert = t('.alert_acl')
end
# redirect_to gestionnaire_groupe_gestionnaire_commentaire_path(@groupe_gestionnaire, @last_commentaire)
rescue Discard::RecordNotDiscarded
flash.alert = t('.alert_already_discarded')
end

View file

@ -7,6 +7,7 @@ module Gestionnaires
end
def show
@unread_commentaires = current_gestionnaire.unread_commentaires?(@groupe_gestionnaire)
end
def edit

View file

@ -0,0 +1,7 @@
class FollowCommentaireGroupeGestionnaire < ApplicationRecord
belongs_to :gestionnaire
belongs_to :groupe_gestionnaire
belongs_to :sender, polymorphic: true, optional: true
validates :gestionnaire_id, uniqueness: { scope: [:groupe_gestionnaire_id, :sender_id, :sender_type, :unfollowed_at] }
end

View file

@ -2,6 +2,7 @@ class Gestionnaire < ApplicationRecord
include UserFindByConcern
has_and_belongs_to_many :groupe_gestionnaires
has_many :commentaire_groupe_gestionnaires
has_many :follow_commentaire_groupe_gestionnaires
belongs_to :user
@ -33,4 +34,27 @@ class Gestionnaire < ApplicationRecord
'Expiré'
end
end
def unread_commentaires?(groupe_gestionnaire)
CommentaireGroupeGestionnaire
.joins(:groupe_gestionnaire)
.joins("LEFT JOIN follow_commentaire_groupe_gestionnaires ON follow_commentaire_groupe_gestionnaires.groupe_gestionnaire_id = commentaire_groupe_gestionnaires.groupe_gestionnaire_id AND follow_commentaire_groupe_gestionnaires.sender_id = commentaire_groupe_gestionnaires.sender_id AND follow_commentaire_groupe_gestionnaires.sender_type = commentaire_groupe_gestionnaires.sender_type AND follow_commentaire_groupe_gestionnaires.gestionnaire_id = #{self.id}")
.where(groupe_gestionnaire: groupe_gestionnaire)
.where('follow_commentaire_groupe_gestionnaires.commentaire_seen_at IS NULL OR follow_commentaire_groupe_gestionnaires.commentaire_seen_at < commentaire_groupe_gestionnaires.created_at')
.exists?
end
def commentaire_seen_at(groupe_gestionnaire, sender)
FollowCommentaireGroupeGestionnaire
.where(gestionnaire: self, groupe_gestionnaire:, sender:)
.order(id: :desc)
.last
&.commentaire_seen_at
end
def mark_commentaire_as_seen(groupe_gestionnaire, sender)
FollowCommentaireGroupeGestionnaire
.where(gestionnaire: self, groupe_gestionnaire: groupe_gestionnaire, sender: sender, unfollowed_at: nil)
.first_or_initialize.update(commentaire_seen_at: Time.zone.now)
end
end

View file

@ -1,6 +1,7 @@
class GroupeGestionnaire < ApplicationRecord
has_many :administrateurs
has_many :commentaire_groupe_gestionnaires
has_many :follow_commentaire_groupe_gestionnaires
has_and_belongs_to_many :gestionnaires
has_ancestry

View file

@ -3,7 +3,7 @@
metadatas: true }
.fr-container
%h2= "Gestion du groupe gestionnaire № #{@groupe_gestionnaire.id}"
%h2= "Gestion du groupe gestionnaire \"#{@groupe_gestionnaire.name}\""
.fr-grid-row.fr-grid-row--gutters.fr-mb-5w
= render GroupeGestionnaire::Card::GestionnairesComponent.new(groupe_gestionnaire: @groupe_gestionnaire, path: admin_groupe_gestionnaire_gestionnaires_path)
= render GroupeGestionnaire::Card::AdministrateursComponent.new(groupe_gestionnaire: @groupe_gestionnaire, path: admin_groupe_gestionnaire_administrateurs_path)

View file

@ -10,6 +10,6 @@
%ul.messages-list{ data: { controller: 'scroll-to' } }
- @groupe_gestionnaire.commentaire_groupe_gestionnaires.where(sender_id: @last_commentaire.sender_id, sender_type: @last_commentaire.sender_type).each do |commentaire|
%li.message{ class: commentaire_is_from_me_class(commentaire, current_gestionnaire), id: dom_id(commentaire) }
= render(GroupeGestionnaire::GroupeGestionnaireCommentaires::CommentaireComponent.new(commentaire: commentaire, connected_user: current_gestionnaire))
= render(GroupeGestionnaire::GroupeGestionnaireCommentaires::CommentaireComponent.new(commentaire: commentaire, connected_user: current_gestionnaire, commentaire_seen_at: @commentaire_seen_at))
- if @last_commentaire.sender
= render partial: "shared/groupe_gestionnaires/commentaires/form", locals: { commentaire: @commentaire, form_url: gestionnaire_groupe_gestionnaire_commentaires_path(@groupe_gestionnaire) }

View file

@ -13,14 +13,14 @@
= t('views.gestionnaires.groupe_gestionnaires.delete')
.fr-container
%h2= "Gestion du groupe gestionnaire № #{@groupe_gestionnaire.id}"
%h2= "Gestion du groupe gestionnaire \"#{@groupe_gestionnaire.name}\""
- if @groupe_gestionnaire.groupe_gestionnaire_id.present?
%p
groupe parent :
%a{ href: gestionnaire_groupe_gestionnaire_path(@groupe_gestionnaire.groupe_gestionnaire) }= @groupe_gestionnaire.groupe_gestionnaire.name
.fr-grid-row.fr-grid-row--gutters.fr-mb-5w
.fr-grid-row.fr-grid-row--gutters.fr-mb-5w.fr-groupe_gestionnaire_cards
= render GroupeGestionnaire::Card::GestionnairesComponent.new(groupe_gestionnaire: @groupe_gestionnaire, path: gestionnaire_groupe_gestionnaire_gestionnaires_path(@groupe_gestionnaire))
= render GroupeGestionnaire::Card::AdministrateursComponent.new(groupe_gestionnaire: @groupe_gestionnaire, path: gestionnaire_groupe_gestionnaire_administrateurs_path(@groupe_gestionnaire))
= render GroupeGestionnaire::Card::ChildrenComponent.new(groupe_gestionnaire: @groupe_gestionnaire, path: gestionnaire_groupe_gestionnaire_children_path(@groupe_gestionnaire))
= render GroupeGestionnaire::Card::CommentairesComponent.new(groupe_gestionnaire: @groupe_gestionnaire, administrateur: nil, path: gestionnaire_groupe_gestionnaire_commentaires_path(@groupe_gestionnaire))
= render GroupeGestionnaire::Card::CommentairesComponent.new(groupe_gestionnaire: @groupe_gestionnaire, administrateur: nil, path: gestionnaire_groupe_gestionnaire_commentaires_path(@groupe_gestionnaire), unread_commentaires: @unread_commentaires)

View file

@ -0,0 +1,16 @@
class CreateFollowCommentaireGroupeGestionnaires < ActiveRecord::Migration[6.1]
disable_ddl_transaction!
def change
create_table "follow_commentaire_groupe_gestionnaires" do |t|
t.references :groupe_gestionnaire, index: { name: :index_follow_commentaire_on_groupe_gestionnaire }
t.references :gestionnaire, null: false, index: { name: :index_follow_commentaire_on_gestionnaire }
t.string "sender_type", null: true
t.bigint "sender_id", null: true
t.datetime "commentaire_seen_at"
t.datetime "unfollowed_at"
t.timestamps
t.index [:gestionnaire_id, :groupe_gestionnaire_id, :sender_id, :sender_type, :unfollowed_at], name: :index_follow_commentaire_on_groupe_gestionnaire_unfollow, unique: true
end
end
end

View file

@ -609,6 +609,20 @@ ActiveRecord::Schema[7.0].define(version: 2024_01_23_085909) do
t.index ["feature_key", "key", "value"], name: "index_flipper_gates_on_feature_key_and_key_and_value", unique: true
end
create_table "follow_commentaire_groupe_gestionnaires", force: :cascade do |t|
t.datetime "commentaire_seen_at", precision: 6
t.datetime "created_at", precision: 6, null: false
t.bigint "gestionnaire_id", null: false
t.bigint "groupe_gestionnaire_id"
t.bigint "sender_id"
t.string "sender_type"
t.datetime "unfollowed_at", precision: 6, precision: nil
t.datetime "updated_at", precision: 6, null: false
t.index ["gestionnaire_id", "groupe_gestionnaire_id", "sender_id", "sender_type", "unfollowed_at"], name: "index_follow_commentaire_on_groupe_gestionnaire_unfollow", unique: true
t.index ["gestionnaire_id"], name: "index_follow_commentaire_on_gestionnaire"
t.index ["groupe_gestionnaire_id"], name: "index_follow_commentaire_on_groupe_gestionnaire"
end
create_table "follows", id: :serial, force: :cascade do |t|
t.datetime "annotations_privees_seen_at", precision: nil, null: false
t.datetime "avis_seen_at", precision: nil, null: false

View file

@ -4,7 +4,7 @@ describe Gestionnaires::GroupeGestionnaireCommentairesController, type: :control
let(:groupe_gestionnaire) { create(:groupe_gestionnaire, gestionnaires: [gestionnaire], administrateurs: [administrateur]) }
let!(:commentaire) { create(:commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, sender: administrateur) }
describe "yyyy#index" do
describe "#index" do
render_views
subject { get :index, params: { groupe_gestionnaire_id: groupe_gestionnaire.id } }
@ -27,7 +27,7 @@ describe Gestionnaires::GroupeGestionnaireCommentairesController, type: :control
end
end
describe "yyyy#show" do
describe "#show" do
render_views
subject { get :show, params: { groupe_gestionnaire_id: groupe_gestionnaire.id, id: commentaire.id } }
@ -50,7 +50,7 @@ describe Gestionnaires::GroupeGestionnaireCommentairesController, type: :control
end
end
describe "yyyy#create" do
describe "#create" do
before do
sign_in(gestionnaire.user)
post :create,
@ -69,7 +69,7 @@ describe Gestionnaires::GroupeGestionnaireCommentairesController, type: :control
end
end
describe "yyyy#destroy" do
describe "#destroy" do
before do
sign_in(gestionnaire.user)
end

View file

@ -0,0 +1,6 @@
FactoryBot.define do
factory :follow_commentaire_groupe_gestionnaire do
association :groupe_gestionnaire
association :gestionnaire
end
end

View file

@ -0,0 +1,7 @@
describe FollowCommentaireGroupeGestionnaire, type: :model do
describe 'associations' do
it { is_expected.to belong_to(:gestionnaire) }
it { is_expected.to belong_to(:groupe_gestionnaire) }
it { is_expected.to belong_to(:sender).optional }
end
end

View file

@ -1,6 +1,7 @@
describe Gestionnaire, type: :model do
describe 'associations' do
it { is_expected.to have_many(:commentaire_groupe_gestionnaires) }
it { is_expected.to have_many(:follow_commentaire_groupe_gestionnaires) }
it { is_expected.to have_and_belong_to_many(:groupe_gestionnaires) }
end
@ -78,4 +79,105 @@ describe Gestionnaire, type: :model do
it { is_expected.to eq [gestionnaire] }
end
end
describe "#unread_commentaires?" do
context "over three different groupe_gestionnaire" do
let(:gestionnaire) { create(:gestionnaire) }
let(:administrateur) { create(:administrateur) }
let(:groupe_gestionnaire) { create(:groupe_gestionnaire, gestionnaires: [gestionnaire]) }
let!(:commentaire_groupe_gestionnaire) { create(:commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, sender: administrateur, created_at: 12.hours.ago) }
let!(:follow_commentaire_groupe_gestionnaire) { create(:follow_commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, gestionnaire: gestionnaire, sender: administrateur, commentaire_seen_at: Time.zone.now) }
let(:gestionnaire_unread_commentaire_cause_never_seen) { create(:gestionnaire) }
let(:administrateur_unread_commentaire_cause_never_seen) { create(:administrateur) }
let(:groupe_gestionnaire_unread_commentaire_cause_never_seen) { create(:groupe_gestionnaire, gestionnaires: [gestionnaire_unread_commentaire_cause_never_seen]) }
let!(:commentaire_groupe_gestionnaire_unread_commentaire_cause_never_seen) { create(:commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire_unread_commentaire_cause_never_seen, sender: administrateur_unread_commentaire_cause_never_seen, created_at: 12.hours.ago) }
let(:gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire) { create(:gestionnaire) }
let(:administrateur_unread_commentaire_cause_seen_at_before_last_commentaire) { create(:administrateur) }
let(:groupe_gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire) { create(:groupe_gestionnaire, gestionnaires: [gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire]) }
let!(:commentaire_groupe_gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire) { create(:commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire, sender: administrateur_unread_commentaire_cause_seen_at_before_last_commentaire, created_at: 12.hours.ago) }
let!(:follow_commentaire_groupe_gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire) { create(:follow_commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire, gestionnaire: gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire, sender: administrateur_unread_commentaire_cause_seen_at_before_last_commentaire, commentaire_seen_at: 1.day.ago) }
it do
expect(gestionnaire.unread_commentaires?(groupe_gestionnaire)).to eq false
expect(gestionnaire_unread_commentaire_cause_never_seen.unread_commentaires?(groupe_gestionnaire_unread_commentaire_cause_never_seen)).to eq true
expect(gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire.unread_commentaires?(groupe_gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire)).to eq true
end
end
context "over same groupe_gestionnaire" do
let(:gestionnaire) { create(:gestionnaire) }
let(:gestionnaire_unread_commentaire_cause_never_seen) { create(:gestionnaire) }
let(:gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire) { create(:gestionnaire) }
let(:administrateur) { create(:administrateur) }
let(:groupe_gestionnaire) { create(:groupe_gestionnaire, gestionnaires: [gestionnaire, gestionnaire_unread_commentaire_cause_never_seen, gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire]) }
let!(:commentaire_groupe_gestionnaire) { create(:commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, sender: administrateur, created_at: 12.hours.ago) }
let!(:follow_commentaire_groupe_gestionnaire) { create(:follow_commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, gestionnaire: gestionnaire, sender: administrateur, commentaire_seen_at: Time.zone.now) }
let!(:follow_commentaire_groupe_gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire) { create(:follow_commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, gestionnaire: gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire, sender: administrateur, commentaire_seen_at: 1.day.ago) }
it do
expect(gestionnaire.unread_commentaires?(groupe_gestionnaire)).to eq false
expect(gestionnaire_unread_commentaire_cause_never_seen.unread_commentaires?(groupe_gestionnaire)).to eq true
expect(gestionnaire_unread_commentaire_cause_seen_at_before_last_commentaire.unread_commentaires?(groupe_gestionnaire)).to eq true
end
end
end
describe "#commentaire_seen_at" do
context "when already seen commentaire" do
let(:gestionnaire) { create(:gestionnaire) }
let(:administrateur) { create(:administrateur) }
let(:groupe_gestionnaire) { create(:groupe_gestionnaire, gestionnaires: [gestionnaire]) }
let!(:commentaire_groupe_gestionnaire) { create(:commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, sender: administrateur, created_at: 12.hours.ago) }
let!(:follow_commentaire_groupe_gestionnaire) { create(:follow_commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, gestionnaire: gestionnaire, sender: administrateur, commentaire_seen_at: Time.zone.now) }
it { expect(gestionnaire.commentaire_seen_at(groupe_gestionnaire, administrateur)).not_to eq nil }
end
context "when never seen commentaire" do
let(:gestionnaire) { create(:gestionnaire) }
let(:administrateur) { create(:administrateur) }
let(:groupe_gestionnaire) { create(:groupe_gestionnaire, gestionnaires: [gestionnaire]) }
let!(:commentaire_groupe_gestionnaire) { create(:commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, sender: administrateur, created_at: 12.hours.ago) }
it { expect(gestionnaire.commentaire_seen_at(groupe_gestionnaire, administrateur)).to eq nil }
end
end
describe "#mark_commentaire_as_seen" do
context "when already seen commentaire" do
let(:now) { Time.zone.now.beginning_of_minute }
let(:gestionnaire) { create(:gestionnaire) }
let(:administrateur) { create(:administrateur) }
let(:groupe_gestionnaire) { create(:groupe_gestionnaire, gestionnaires: [gestionnaire]) }
let!(:commentaire_groupe_gestionnaire) { create(:commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, sender: administrateur, created_at: 12.hours.ago) }
let!(:follow_commentaire_groupe_gestionnaire) { create(:follow_commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, gestionnaire: gestionnaire, sender: administrateur, commentaire_seen_at: 12.hours.ago) }
before do
Timecop.freeze(now) do
gestionnaire.mark_commentaire_as_seen(groupe_gestionnaire, administrateur)
end
end
it { expect(gestionnaire.commentaire_seen_at(groupe_gestionnaire, administrateur)).to eq now }
end
context "when never seen commentaire" do
let(:now) { Time.zone.now.beginning_of_minute }
let(:gestionnaire) { create(:gestionnaire) }
let(:administrateur) { create(:administrateur) }
let(:groupe_gestionnaire) { create(:groupe_gestionnaire, gestionnaires: [gestionnaire]) }
let!(:commentaire_groupe_gestionnaire) { create(:commentaire_groupe_gestionnaire, groupe_gestionnaire: groupe_gestionnaire, sender: administrateur, created_at: 12.hours.ago) }
before do
Timecop.freeze(now) do
gestionnaire.mark_commentaire_as_seen(groupe_gestionnaire, administrateur)
end
end
it { expect(gestionnaire.commentaire_seen_at(groupe_gestionnaire, administrateur)).to eq now }
end
end
end

View file

@ -2,6 +2,7 @@ describe GroupeGestionnaire, type: :model do
describe 'associations' do
it { is_expected.to have_many(:administrateurs) }
it { is_expected.to have_many(:commentaire_groupe_gestionnaires) }
it { is_expected.to have_many(:follow_commentaire_groupe_gestionnaires) }
it { is_expected.to have_and_belong_to_many(:gestionnaires) }
end