From 725a97da7e41d94f6ac593c1f872cbc6c1fd8596 Mon Sep 17 00:00:00 2001
From: Lisa Durand <lisa.c.durand@gmail.com>
Date: Wed, 16 Oct 2024 14:31:08 +0200
Subject: [PATCH] admin can modify procedure labels

---
 .../procedure/card/labels_component.rb        |   7 +
 .../labels_component/labels_component.fr.yml  |   3 +
 .../labels_component.html.haml                |  17 ++
 .../procedure_labels_controller.rb            |  66 +++++++
 app/helpers/dossier_helper.rb                 |   2 +-
 app/models/label.rb                           |  28 ++-
 .../procedure_labels/_form.html.haml          |   8 +
 .../procedure_labels/edit.html.haml           |  16 ++
 .../procedure_labels/index.html.haml          |  35 ++++
 .../procedure_labels/new.html.haml            |  16 ++
 .../administrateurs/procedures/show.html.haml |   1 +
 .../dossiers/_header_top.html.haml            |   9 +-
 config/locales/models/procedure_label/fr.yml  |   6 +
 config/routes.rb                              |   2 +
 .../procedure_labels_controller_spec.rb       | 168 ++++++++++++++++++
 spec/factories/procedure_label.rb             |   9 +
 16 files changed, 388 insertions(+), 5 deletions(-)
 create mode 100644 app/components/procedure/card/labels_component.rb
 create mode 100644 app/components/procedure/card/labels_component/labels_component.fr.yml
 create mode 100644 app/components/procedure/card/labels_component/labels_component.html.haml
 create mode 100644 app/controllers/administrateurs/procedure_labels_controller.rb
 create mode 100644 app/views/administrateurs/procedure_labels/_form.html.haml
 create mode 100644 app/views/administrateurs/procedure_labels/edit.html.haml
 create mode 100644 app/views/administrateurs/procedure_labels/index.html.haml
 create mode 100644 app/views/administrateurs/procedure_labels/new.html.haml
 create mode 100644 config/locales/models/procedure_label/fr.yml
 create mode 100644 spec/controllers/administrateurs/procedure_labels_controller_spec.rb
 create mode 100644 spec/factories/procedure_label.rb

diff --git a/app/components/procedure/card/labels_component.rb b/app/components/procedure/card/labels_component.rb
new file mode 100644
index 000000000..4b484db35
--- /dev/null
+++ b/app/components/procedure/card/labels_component.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class Procedure::Card::LabelsComponent < ApplicationComponent
+  def initialize(procedure:)
+    @procedure = procedure
+  end
+end
diff --git a/app/components/procedure/card/labels_component/labels_component.fr.yml b/app/components/procedure/card/labels_component/labels_component.fr.yml
new file mode 100644
index 000000000..31f598407
--- /dev/null
+++ b/app/components/procedure/card/labels_component/labels_component.fr.yml
@@ -0,0 +1,3 @@
+---
+fr:
+  title: Labels
diff --git a/app/components/procedure/card/labels_component/labels_component.html.haml b/app/components/procedure/card/labels_component/labels_component.html.haml
new file mode 100644
index 000000000..456f74ec7
--- /dev/null
+++ b/app/components/procedure/card/labels_component/labels_component.html.haml
@@ -0,0 +1,17 @@
+.fr-col-6.fr-col-md-4.fr-col-lg-3
+  = link_to  admin_procedure_procedure_labels_path(@procedure), class: 'fr-tile fr-enlarge-link' do
+    .fr-tile__body.flex.column.align-center.justify-between
+      - if @procedure.procedure_labels.present?
+        %p.fr-badge.fr-badge--info
+          Configuré
+        %div
+          .line-count.fr-my-1w
+            %p.fr-tag= @procedure.procedure_labels.size
+      - else
+        %p.fr-badge
+          Non configuré
+
+      %h3.fr-h6
+        = t('.title')
+      %p.fr-tile-subtitle Gérer les labels utilisables par les instructeurs
+      %p.fr-btn.fr-btn--tertiary= t('views.shared.actions.edit')
diff --git a/app/controllers/administrateurs/procedure_labels_controller.rb b/app/controllers/administrateurs/procedure_labels_controller.rb
new file mode 100644
index 000000000..b77800116
--- /dev/null
+++ b/app/controllers/administrateurs/procedure_labels_controller.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module Administrateurs
+  class ProcedureLabelsController < AdministrateurController
+    before_action :retrieve_procedure
+    before_action :set_colors_collection, only: [:edit, :new, :create, :update]
+
+    def index
+      @labels = @procedure.procedure_labels
+    end
+
+    def edit
+      @label = label
+    end
+
+    def new
+      @label = ProcedureLabel.new
+    end
+
+    def create
+      @label = @procedure.procedure_labels.build(procedure_label_params)
+
+      if @label.save
+        flash.notice = 'Le label a bien été créé'
+        redirect_to admin_procedure_procedure_labels_path(@procedure)
+      else
+        flash.alert = @label.errors.full_messages
+        render :new
+      end
+    end
+
+    def update
+      @label = label
+      @label.update(procedure_label_params)
+
+      if @label.valid?
+        flash.notice = 'Le label a bien été modifié'
+        redirect_to admin_procedure_procedure_labels_path(@procedure)
+      else
+        flash.alert = @label.errors.full_messages
+        render :edit
+      end
+    end
+
+    def destroy
+      @label = label
+      @label.destroy!
+      flash.notice = 'Le label a bien été supprimé'
+      redirect_to admin_procedure_procedure_labels_path(@procedure)
+    end
+
+    private
+
+    def procedure_label_params
+      params.require(:procedure_label).permit(:name, :color)
+    end
+
+    def label
+      @procedure.procedure_labels.find(params[:id])
+    end
+
+    def set_colors_collection
+      @colors_collection = ProcedureLabel.colors.values
+    end
+  end
+end
diff --git a/app/helpers/dossier_helper.rb b/app/helpers/dossier_helper.rb
index 1ef31e14d..ba6a1004c 100644
--- a/app/helpers/dossier_helper.rb
+++ b/app/helpers/dossier_helper.rb
@@ -130,7 +130,7 @@ module DossierHelper
   end
 
   def tag_label(name, color)
-    tag.span(name, class: "fr-tag fr-tag--sm fr-tag--#{color}")
+    tag.span(name, class: "fr-tag fr-tag--sm fr-tag--#{ProcedureLabel.colors.fetch(color.underscore)}")
   end
 
   def demandeur_dossier(dossier)
diff --git a/app/models/label.rb b/app/models/label.rb
index 6c4e73e13..bebfddf21 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -4,11 +4,33 @@ class Label < ApplicationRecord
   belongs_to :procedure
   has_many :dossier_labels, dependent: :destroy
 
+  NAME_MAX_LENGTH = 30
   GENERIC_LABELS = [
-    { name: 'à relancer', color: 'brown-caramel' },
-    { name: 'complet', color: 'green-bourgeon' },
-    { name: 'prêt pour validation', color: 'green-archipel' }
+    { name: 'à relancer', color: 'brown_caramel' },
+    { name: 'complet', color: 'green_bourgeon' },
+    { name: 'prêt pour validation', color: 'green_archipel' }
   ]
 
+  enum color: {
+    green_tilleul_verveine: "green-tilleul-verveine",
+    green_bourgeon: "green-bourgeon",
+    green_emeraude: "green-emeraude",
+    green_menthe: "green-menthe",
+    green_archipel: "green-archipel",
+    blue_ecume: "blue-ecume",
+    blue_cumulus: "blue-cumulus",
+    purple_glycine: "purple-glycine",
+    pink_macaron: "pink-macaron",
+    pink_tuile: "pink-tuile",
+    yellow_tournesol: "yellow-tournesol",
+    yellow_moutarde: "yellow-moutarde",
+    orange_terre_battue: "orange-terre-battue",
+    brown_cafe_creme: "brown-cafe-creme",
+    brown_caramel: "brown-caramel",
+    brown_opera: "brown-opera",
+    beige_gris_galet: "beige-gris-galet"
+  }
+
   validates :name, :color, presence: true
+  validates :name, length: { maximum: NAME_MAX_LENGTH }
 end
diff --git a/app/views/administrateurs/procedure_labels/_form.html.haml b/app/views/administrateurs/procedure_labels/_form.html.haml
new file mode 100644
index 000000000..a37eb26c5
--- /dev/null
+++ b/app/views/administrateurs/procedure_labels/_form.html.haml
@@ -0,0 +1,8 @@
+= form_with model: label, url: admin_procedure_procedure_labels_path(@procedure, id: @label.id), local: true do |f|
+  = render Dsfr::InputComponent.new(form: f, attribute: :name, input_type: :text_field, opts: { maxlength: ProcedureLabel::NAME_MAX_LENGTH})
+  = f.label :color, class: 'fr-label' do
+    = t('activerecord.attributes.procedure_label.color')
+    = render EditableChamp::AsteriskMandatoryComponent.new
+  = f.select :color, options_for_select(@colors_collection, selected: @label.color), {prompt: 'Choisir une couleur'}, {class: 'fr-select'}
+
+  = render Procedure::FixedFooterComponent.new(procedure: @procedure, form: f)
diff --git a/app/views/administrateurs/procedure_labels/edit.html.haml b/app/views/administrateurs/procedure_labels/edit.html.haml
new file mode 100644
index 000000000..fd712d69a
--- /dev/null
+++ b/app/views/administrateurs/procedure_labels/edit.html.haml
@@ -0,0 +1,16 @@
+= render partial: 'administrateurs/breadcrumbs',
+  locals: { steps: [['Démarches', admin_procedures_path],
+                    [@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)],
+                    ['gestion des labels', admin_procedure_procedure_labels_path(procedure_id: @procedure.id)],
+                    ['Modifier le label']] }
+
+
+.fr-container
+  .fr-mb-3w
+    = link_to "Liste de tous les labels", admin_procedure_procedure_labels_path(procedure_id: @procedure.id), class: "fr-link fr-icon-arrow-left-line fr-link--icon-left"
+
+  %h1.fr-h2
+    Modifier le label
+
+  = render partial: 'form',
+    locals: { label: @label, procedure_id: @procedure.id }
diff --git a/app/views/administrateurs/procedure_labels/index.html.haml b/app/views/administrateurs/procedure_labels/index.html.haml
new file mode 100644
index 000000000..bb5f219e0
--- /dev/null
+++ b/app/views/administrateurs/procedure_labels/index.html.haml
@@ -0,0 +1,35 @@
+= render partial: 'administrateurs/breadcrumbs',
+  locals: { steps: [['Démarches', admin_procedures_path],
+                    [@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)],
+                    ['Labels']] }
+
+.fr-container
+  %h1.fr-h2 Labels
+
+  = link_to "Nouveau label", new_admin_procedure_procedure_label_path(procedure_id: @procedure.id), class: "fr-btn fr-btn--primary fr-btn--icon-left fr-icon-add-circle-line mb-3"
+
+  - if @procedure.procedure_labels.present?
+    .fr-table.fr-table--layout-fixed.fr-table--bordered
+      %table
+        %caption Liste des labels
+        %thead
+          %tr
+            %th{ scope: "col" }
+              Nom
+            %th.change{ scope: "col" }
+              Actions
+
+        %tbody
+          - @labels.each do |label|
+            %tr
+              %td
+                = tag_label(label.name, label.color)
+              %td.change
+                = link_to('Modifier', edit_admin_procedure_procedure_label_path(procedure_id: @procedure.id, id: label.id), class: 'fr-btn fr-btn--sm fr-btn--secondary fr-btn--icon-left fr-icon-pencil-line')
+                = link_to 'Supprimer',
+                  admin_procedure_procedure_label_path(procedure_id: @procedure.id, id: label.id),
+                  method: :delete,
+                  data: { confirm: "Confirmez vous la suppression de #{label.name}" },
+                  class: 'fr-btn fr-btn--sm fr-btn--secondary fr-btn--icon-left fr-icon-delete-line fr-ml-1w'
+
+= render Procedure::FixedFooterComponent.new(procedure: @procedure)
diff --git a/app/views/administrateurs/procedure_labels/new.html.haml b/app/views/administrateurs/procedure_labels/new.html.haml
new file mode 100644
index 000000000..a6bed20ad
--- /dev/null
+++ b/app/views/administrateurs/procedure_labels/new.html.haml
@@ -0,0 +1,16 @@
+= render partial: 'administrateurs/breadcrumbs',
+  locals: { steps: [['Démarches', admin_procedures_path],
+                    [@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)],
+                    ['gestion des labels', admin_procedure_procedure_labels_path(procedure_id: @procedure.id)],
+                    ['Nouveau label']] }
+
+
+.fr-container
+  .fr-mb-3w
+    = link_to "Liste de tous les labels", admin_procedure_procedure_labels_path(procedure_id: @procedure.id), class: "fr-link fr-icon-arrow-left-line fr-link--icon-left"
+
+  %h1.fr-h2
+    Créer un nouveau label
+
+  = render partial: 'form',
+    locals: { label: @label, procedure_id: @procedure.id }
diff --git a/app/views/administrateurs/procedures/show.html.haml b/app/views/administrateurs/procedures/show.html.haml
index 4414f921f..877f4ef98 100644
--- a/app/views/administrateurs/procedures/show.html.haml
+++ b/app/views/administrateurs/procedures/show.html.haml
@@ -98,3 +98,4 @@
     = render Procedure::Card::DossierSubmittedMessageComponent.new(procedure: @procedure)
     = render Procedure::Card::ChorusComponent.new(procedure: @procedure)
     = render Procedure::Card::AccuseLectureComponent.new(procedure: @procedure)
+    = render Procedure::Card::LabelsComponent.new(procedure: @procedure)
diff --git a/app/views/instructeurs/dossiers/_header_top.html.haml b/app/views/instructeurs/dossiers/_header_top.html.haml
index c0f406abb..7fe1fb2f8 100644
--- a/app/views/instructeurs/dossiers/_header_top.html.haml
+++ b/app/views/instructeurs/dossiers/_header_top.html.haml
@@ -44,4 +44,11 @@
                 .fr-fieldset__element
                   .fr-checkbox-group.fr-checkbox-group--sm.fr-mb-1w
                     = b.check_box(checked: DossierLabel.find_by(dossier_id: dossier.id, label_id: b.value).present?  )
-                    = b.label(class: "fr-label fr-tag fr-tag--sm fr-tag--#{b.object.color}") { b.text }
+                    = b.label(class: "fr-label fr-tag fr-tag--sm fr-tag--#{Label.colors.fetch(b.object.color)}") { b.text }
+
+            %hr
+              %p.fr-text--sm.fr-text-mention--grey.fr-mb-0
+                %b Besoin d'autres labels ?
+                %br
+                Contactez les
+                = link_to 'administrateurs de la démarche', administrateurs_instructeur_procedure_path(dossier.procedure), class: 'fr-link fr-link--sm', **external_link_attributes
diff --git a/config/locales/models/procedure_label/fr.yml b/config/locales/models/procedure_label/fr.yml
new file mode 100644
index 000000000..deabcbd8e
--- /dev/null
+++ b/config/locales/models/procedure_label/fr.yml
@@ -0,0 +1,6 @@
+fr:
+  activerecord:
+    attributes:
+      procedure_label:
+        color: Couleur
+        name: Nom
diff --git a/config/routes.rb b/config/routes.rb
index 5b6dd9634..0bf0c4602 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -708,6 +708,8 @@ Rails.application.routes.draw do
         get 'preview', on: :member
       end
 
+      resources :procedure_labels, controller: 'procedure_labels'
+
       resource :attestation_template, only: [:show, :edit, :update, :create] do
         get 'preview', on: :member
       end
diff --git a/spec/controllers/administrateurs/procedure_labels_controller_spec.rb b/spec/controllers/administrateurs/procedure_labels_controller_spec.rb
new file mode 100644
index 000000000..4d93b4271
--- /dev/null
+++ b/spec/controllers/administrateurs/procedure_labels_controller_spec.rb
@@ -0,0 +1,168 @@
+# frozen_string_literal: true
+
+describe Administrateurs::ProcedureLabelsController, type: :controller do
+  let(:admin) { administrateurs(:default_admin) }
+  let(:procedure) { create(:procedure, administrateur: admin) }
+  let(:admin_2) { create(:administrateur) }
+  let(:procedure_2) { create(:procedure, administrateur: admin_2) }
+
+  describe '#index' do
+    render_views
+    let!(:label_1) { create(:procedure_label, procedure:) }
+    let!(:label_2) { create(:procedure_label, procedure:) }
+    let!(:label_3) { create(:procedure_label, procedure:) }
+
+    before do
+      sign_in(admin.user)
+    end
+
+    subject { get :index, params: { procedure_id: procedure.id } }
+
+    it 'displays all procedure labels' do
+      subject
+      expect(response.body).to have_link("Nouveau label")
+      expect(response.body).to have_link("Modifier", count: 3)
+      expect(response.body).to have_link("Supprimer", count: 3)
+    end
+  end
+
+  describe '#create' do
+    before do
+      sign_in(admin.user)
+    end
+
+    subject { post :create, params: params }
+
+    context 'when submitting a new label' do
+      let(:params) do
+        {
+          procedure_label: {
+            name: 'Nouveau label',
+                    color: 'green-bourgeon'
+          },
+        procedure_id: procedure.id
+        }
+      end
+
+      it { expect { subject }.to change { ProcedureLabel.count } .by(1) }
+
+      it 'creates a new label' do
+        subject
+        expect(flash.alert).to be_nil
+        expect(flash.notice).to eq('Le label a bien été créé')
+        expect(ProcedureLabel.last.name).to eq('Nouveau label')
+        expect(ProcedureLabel.last.color).to eq('green_bourgeon')
+        expect(procedure.procedure_labels.last).to eq(ProcedureLabel.last)
+      end
+    end
+
+    context 'when submitting an invalid label' do
+      let(:params) { { procedure_label: { name: 'Nouveau label' }, procedure_id: procedure.id } }
+
+      it { expect { subject }.not_to change { ProcedureLabel.count } }
+
+      it 'does not create a new label' do
+        subject
+        expect(flash.alert).to eq(["Le champ « Couleur » doit être rempli"])
+        expect(response).to render_template(:new)
+        expect(assigns(:label).name).to eq('Nouveau label')
+      end
+    end
+
+    context 'when submitting a label for a not own procedure' do
+      let(:params) do
+        {
+          procedure_label: {
+            name: 'Nouveau label',
+            color: 'green-bourgeon'
+          },
+        procedure_id: procedure_2.id
+        }
+      end
+
+      it { expect { subject }.not_to change { ProcedureLabel.count } }
+
+      it 'does not create a new label' do
+        subject
+        expect(flash.alert).to eq("Démarche inexistante")
+        expect(response.status).to eq(404)
+      end
+    end
+  end
+
+  describe '#update' do
+    let!(:label) { create(:procedure_label, procedure:) }
+    let(:label_params) { { name: 'Nouveau nom' } }
+    let(:params) { { id: label.id, procedure_label: label_params, procedure_id: procedure.id } }
+
+    before do
+      sign_in(admin.user)
+    end
+
+    subject { patch :update, params: }
+
+    context 'when updating a label' do
+      it 'updates correctly' do
+        subject
+        expect(flash.alert).to be_nil
+        expect(flash.notice).to eq('Le label a bien été modifié')
+        expect(label.reload.name).to eq('Nouveau nom')
+        expect(label.reload.color).to eq('green_bourgeon')
+        expect(label.reload.updated_at).not_to eq(label.reload.created_at)
+        expect(response).to redirect_to(admin_procedure_procedure_labels_path(procedure_id: procedure.id))
+      end
+    end
+
+    context 'when updating a service with invalid data' do
+      let(:label_params) { { name: '' } }
+
+      it 'does not update' do
+        subject
+        expect(flash.alert).not_to be_nil
+        expect(response).to render_template(:edit)
+        expect(label.reload.updated_at).to eq(label.reload.created_at)
+      end
+    end
+
+    context 'when updating a label for a not own procedure' do
+      let(:params) { { id: label.id, procedure_label: label_params, procedure_id: procedure_2.id } }
+
+      it 'does not update' do
+        subject
+        expect(label.reload.updated_at).to eq(label.reload.created_at)
+      end
+    end
+  end
+
+  describe '#destroy' do
+    let(:label) { create(:procedure_label, procedure:) }
+
+    before do
+      sign_in(admin.user)
+    end
+
+    subject { delete :destroy, params: }
+
+    context "when deleting a label" do
+      let(:params) { { id: label.id, procedure_id: procedure.id } }
+
+      it "delete the label" do
+        subject
+        expect { label.reload }.to raise_error(ActiveRecord::RecordNotFound)
+        expect(flash.notice).to eq('Le label a bien été supprimé')
+        expect(response).to redirect_to((admin_procedure_procedure_labels_path(procedure_id: procedure.id)))
+      end
+    end
+
+    context 'when deleting a label for a not own procedure' do
+      let(:params) { { id: label.id, procedure_id: procedure_2.id } }
+
+      it 'does not delete' do
+        subject
+        expect(flash.alert).to eq("Démarche inexistante")
+        expect(response.status).to eq(404)
+        expect { label.reload }.not_to raise_error(ActiveRecord::RecordNotFound)
+      end
+    end
+  end
+end
diff --git a/spec/factories/procedure_label.rb b/spec/factories/procedure_label.rb
new file mode 100644
index 000000000..fbc907b14
--- /dev/null
+++ b/spec/factories/procedure_label.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+  factory :procedure_label do
+    name { 'Un label' }
+    color { 'green-bourgeon' }
+    association :procedure
+  end
+end