Merge pull request #8727 from tchak/annotations-autosave
feat(annotations): activate autosave
This commit is contained in:
commit
a119a301bd
24 changed files with 146 additions and 117 deletions
15
app/components/dossiers/autosave_footer_component.rb
Normal file
15
app/components/dossiers/autosave_footer_component.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
class Dossiers::AutosaveFooterComponent < ApplicationComponent
|
||||
include ApplicationHelper
|
||||
attr_reader :dossier
|
||||
|
||||
def initialize(dossier:, annotation:)
|
||||
@dossier = dossier
|
||||
@annotation = annotation
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def annotation?
|
||||
@annotation
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
en:
|
||||
brouillon:
|
||||
explanation: Your draft is automatically saved.
|
||||
confirmation: Draft saved
|
||||
error: Impossible to save the draft
|
||||
en_construction:
|
||||
explanation: Your file is automatically saved.
|
||||
confirmation: File saved
|
||||
error: Impossible to save the file
|
||||
annotations:
|
||||
explanation: Your annotations are automatically saved.
|
||||
confirmation: Annotations saved
|
||||
error: Impossible to save the annotations
|
||||
more_information: More informations
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
fr:
|
||||
brouillon:
|
||||
explanation: Votre brouillon est automatiquement enregistré.
|
||||
confirmation: Brouillon enregistré
|
||||
error: Impossible d’enregistrer le brouillon
|
||||
en_construction:
|
||||
explanation: Votre dossier est automatiquement enregistré.
|
||||
confirmation: Dossier enregistré
|
||||
error: Impossible d’enregistrer le dossier
|
||||
annotations:
|
||||
explanation: Vos annotations sont automatiquement enregistrées.
|
||||
confirmation: Annotations enregistré
|
||||
error: Impossible d’enregistrer les annotations
|
||||
more_information: En savoir plus
|
|
@ -0,0 +1,37 @@
|
|||
.autosave.autosave-state-idle{ data: { controller: 'autosave-status' } }
|
||||
%p.autosave-explanation.fr-text--sm
|
||||
%span.autosave-explanation-text
|
||||
- if annotation?
|
||||
= t('.annotations.explanation')
|
||||
- elsif dossier.brouillon?
|
||||
= t('.brouillon.explanation')
|
||||
- else
|
||||
= t('.en_construction.explanation')
|
||||
- if !annotation?
|
||||
= link_to t('.more_information'), t("links.common.faq.autosave_url"), class: 'autosave-more-infos fr-link fr-link--sm', **external_link_attributes
|
||||
|
||||
%p.autosave-status.succeeded
|
||||
%span.autosave-icon.icon.accept
|
||||
%span.autosave-label
|
||||
- if annotation?
|
||||
= t('.annotations.confirmation')
|
||||
- elsif dossier.brouillon?
|
||||
= t('.brouillon.confirmation')
|
||||
- else
|
||||
= t('.en_construction.confirmation')
|
||||
- if !annotation?
|
||||
= link_to t('.more_information'), t("links.common.faq.autosave_url"), class: 'autosave-more-infos fr-link fr-link--sm', **external_link_attributes
|
||||
|
||||
%p.autosave-status.failed
|
||||
%span.autosave-icon ⚠️
|
||||
%span.autosave-label
|
||||
- if annotation?
|
||||
= t('.annotations.error')
|
||||
- elsif dossier.brouillon?
|
||||
= t('.brouillon.error')
|
||||
- else
|
||||
= t('.en_construction.error')
|
||||
%button.button.small.autosave-retry{ type: :button, data: { action: 'autosave-status#onClickRetryButton', autosave_status_target: 'retryButton' } }
|
||||
%span.autosave-retry-label réessayer
|
||||
%span.autosave-retrying-label enregistrement en cours…
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
class Dossiers::EditFooterComponent < ApplicationComponent
|
||||
def initialize(dossier:)
|
||||
def initialize(dossier:, annotation:)
|
||||
@dossier = dossier
|
||||
@annotation = annotation
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -9,6 +10,10 @@ class Dossiers::EditFooterComponent < ApplicationComponent
|
|||
controller.current_user.owns?(@dossier)
|
||||
end
|
||||
|
||||
def annotation?
|
||||
@annotation.present?
|
||||
end
|
||||
|
||||
def button_options
|
||||
{
|
||||
class: 'fr-btn fr-btn--sm',
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
.dossier-edit-sticky-footer
|
||||
.send-dossier-actions-bar
|
||||
= render partial: 'shared/dossiers/autosave', locals: { dossier: @dossier }
|
||||
= render Dossiers::AutosaveFooterComponent.new(dossier: @dossier, annotation: annotation?)
|
||||
|
||||
- if @dossier.can_transition_to_en_construction?
|
||||
- if !annotation? && @dossier.can_transition_to_en_construction?
|
||||
= button_to t('.submit'), brouillon_dossier_url(@dossier), button_options
|
||||
|
||||
- if @dossier.brouillon? && !owner?
|
||||
.send-notice.invite-cannot-submit
|
||||
= t('.invite_notice')
|
||||
|
||||
= render partial: "shared/dossiers/submit_is_over", locals: { dossier: @dossier }
|
||||
- if !annotation?
|
||||
= render partial: "shared/dossiers/submit_is_over", locals: { dossier: @dossier }
|
||||
|
|
|
@ -8,4 +8,4 @@
|
|||
|
||||
- if @champ.rebased_at.present? && @champ.rebased_at > (@seen_at || @champ.updated_at) && current_user.owns_or_invite?(@champ.dossier)
|
||||
%span.updated-at.highlighted
|
||||
Le type de ce @champ où sa description a été modifiée par l'administration. Vérifier son contenu.
|
||||
Le type de ce champ ou sa description ont été modifiés par l'administration. Vérifier son contenu.
|
||||
|
|
|
@ -28,12 +28,7 @@ class EditableChamp::EditableChampComponent < ApplicationComponent
|
|||
def stimulus_controller
|
||||
if !@champ.block? && @champ.fillable?
|
||||
# This is an editable champ. Lets find what controllers it might need.
|
||||
controllers = []
|
||||
|
||||
# This is a public champ – it can have an autosave controller.
|
||||
if @champ.public?
|
||||
controllers << 'autosave'
|
||||
end
|
||||
controllers = ['autosave']
|
||||
|
||||
# This is a dropdown champ. Activate special behaviours it might have.
|
||||
if @champ.simple_drop_down_list? || @champ.linked_drop_down_list?
|
||||
|
|
|
@ -60,10 +60,6 @@ class TypesDeChampEditor::ChampComponent < ApplicationComponent
|
|||
|
||||
TypeDeChamp.type_champs
|
||||
.keys
|
||||
# FIXME
|
||||
# We can only refresh after update champs when autosave is enabled. And it is disabled for now in private forms.
|
||||
# So for new we restrict champs that require refresh after update to public forms.
|
||||
.filter { type_de_champ.public? || !TypeDeChamp.refresh_after_update?(_1) }
|
||||
.filter(&method(:filter_type_champ))
|
||||
.filter(&method(:filter_featured_type_champ))
|
||||
.filter(&method(:filter_block_type_champ))
|
||||
|
|
22
app/controllers/concerns/turbo_champs_concern.rb
Normal file
22
app/controllers/concerns/turbo_champs_concern.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
module TurboChampsConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
private
|
||||
|
||||
def champs_to_turbo_update(params, champs)
|
||||
champ_ids = params.keys.map(&:to_i)
|
||||
|
||||
to_update = champs.filter { _1.id.in?(champ_ids) && _1.refresh_after_update? }
|
||||
to_show, to_hide = champs.filter(&:conditional?)
|
||||
.partition(&:visible?)
|
||||
.map { champs_to_one_selector(_1 - to_update) }
|
||||
|
||||
return to_show, to_hide, to_update
|
||||
end
|
||||
|
||||
def champs_to_one_selector(champs)
|
||||
champs
|
||||
.map { "##{_1.input_group_id}" }
|
||||
.join(',')
|
||||
end
|
||||
end
|
|
@ -4,6 +4,7 @@ module Instructeurs
|
|||
include ActionView::Helpers::TextHelper
|
||||
include CreateAvisConcern
|
||||
include DossierHelper
|
||||
include TurboChampsConcern
|
||||
|
||||
include ActionController::Streaming
|
||||
include Zipline
|
||||
|
@ -244,12 +245,14 @@ module Instructeurs
|
|||
if dossier.champs_private_all.any?(&:changed?)
|
||||
dossier.last_champ_private_updated_at = Time.zone.now
|
||||
end
|
||||
dossier.save
|
||||
dossier.log_modifier_annotations!(current_instructeur)
|
||||
if !dossier.save(context: :annotations)
|
||||
flash.now.alert = dossier.errors.full_messages
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to annotations_privees_instructeur_dossier_path(procedure, dossier) }
|
||||
format.turbo_stream
|
||||
format.turbo_stream do
|
||||
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_private_params.fetch(:champs_private_all_attributes), dossier.champs_private_all)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
module Users
|
||||
class DossiersController < UserController
|
||||
include DossierHelper
|
||||
include TurboChampsConcern
|
||||
|
||||
layout 'procedure_context', only: [:identite, :update_identite, :siret, :update_siret]
|
||||
|
||||
|
@ -198,7 +199,7 @@ module Users
|
|||
respond_to do |format|
|
||||
format.html { render :brouillon }
|
||||
format.turbo_stream do
|
||||
@to_show, @to_hide, @to_update = champs_to_turbo_update
|
||||
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_params.fetch(:champs_public_all_attributes), dossier.champs_public_all)
|
||||
|
||||
render(:update, layout: false)
|
||||
end
|
||||
|
@ -216,7 +217,7 @@ module Users
|
|||
respond_to do |format|
|
||||
format.html { render :modifier }
|
||||
format.turbo_stream do
|
||||
@to_show, @to_hide, @to_update = champs_to_turbo_update
|
||||
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_params.fetch(:champs_public_all_attributes), dossier.champs_public_all)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -483,24 +484,6 @@ module Users
|
|||
errors
|
||||
end
|
||||
|
||||
def champs_to_turbo_update
|
||||
champ_ids = champs_public_params
|
||||
.fetch(:champs_public_all_attributes)
|
||||
.keys
|
||||
.map(&:to_i)
|
||||
|
||||
to_update = dossier
|
||||
.champs_public_all
|
||||
.filter { _1.id.in?(champ_ids) && _1.refresh_after_update? }
|
||||
to_show, to_hide = dossier
|
||||
.champs_public_all
|
||||
.filter(&:conditional?)
|
||||
.partition(&:visible?)
|
||||
.map { champs_to_one_selector(_1 - to_update) }
|
||||
|
||||
return to_show, to_hide, to_update
|
||||
end
|
||||
|
||||
def ensure_ownership!
|
||||
if !current_user.owns?(dossier)
|
||||
forbidden!
|
||||
|
@ -561,12 +544,5 @@ module Users
|
|||
submit_validation_options
|
||||
end
|
||||
end
|
||||
|
||||
def champs_to_one_selector(champs)
|
||||
champs
|
||||
.map(&:input_group_id)
|
||||
.map { |id| "##{id}" }
|
||||
.join(',')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,8 +21,6 @@ module Mutations
|
|||
end
|
||||
|
||||
if annotation.save
|
||||
dossier.log_modifier_annotation!(annotation, instructeur)
|
||||
|
||||
{ annotation: }
|
||||
else
|
||||
{ errors: annotation.errors.full_messages }
|
||||
|
|
|
@ -45,10 +45,7 @@ export class AutosaveController extends ApplicationController {
|
|||
this.#latestPromise = Promise.resolve();
|
||||
this.onGlobal('autosave:retry', () => this.didRequestRetry());
|
||||
this.on('change', (event) => this.onChange(event));
|
||||
|
||||
if (this.saveOnInput) {
|
||||
this.on('input', (event) => this.onInput(event));
|
||||
}
|
||||
this.on('input', (event) => this.onInput(event));
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
|
@ -86,8 +83,7 @@ export class AutosaveController extends ApplicationController {
|
|||
this.debounce(this.enqueueAutosaveRequest, AUTOSAVE_DEBOUNCE_DELAY);
|
||||
} else if (
|
||||
isSelectElement(target) ||
|
||||
isCheckboxOrRadioInputElement(target) ||
|
||||
(!this.saveOnInput && isTextInputElement(target))
|
||||
isCheckboxOrRadioInputElement(target)
|
||||
) {
|
||||
// Wait next tick so champs having JS can interact
|
||||
// with form elements before extracting form data.
|
||||
|
@ -138,10 +134,6 @@ export class AutosaveController extends ApplicationController {
|
|||
}, AUTOSAVE_CONDITIONAL_SPINNER_DEBOUNCE_DELAY);
|
||||
}
|
||||
|
||||
private get saveOnInput() {
|
||||
return !!this.form?.dataset.saveOnInput;
|
||||
}
|
||||
|
||||
private didRequestRetry() {
|
||||
if (this.#needsRetry) {
|
||||
this.enqueueAutosaveRequest();
|
||||
|
|
|
@ -1062,16 +1062,6 @@ class Dossier < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def log_modifier_annotations!(instructeur)
|
||||
champs_private.filter(&:value_previously_changed?).each do |champ|
|
||||
log_dossier_operation(instructeur, :modifier_annotation, champ)
|
||||
end
|
||||
end
|
||||
|
||||
def log_modifier_annotation!(champ, instructeur)
|
||||
log_dossier_operation(instructeur, :modifier_annotation, champ)
|
||||
end
|
||||
|
||||
def demander_un_avis!(avis)
|
||||
log_dossier_operation(avis.claimant, :demander_un_avis, avis)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
- if @to_show.present?
|
||||
= turbo_stream.show_all(@to_show)
|
||||
- if @to_hide.present?
|
||||
= turbo_stream.hide_all(@to_hide)
|
||||
- @to_update.each do |champ|
|
||||
= fields_for champ.input_name, champ do |form|
|
||||
= turbo_stream.replace champ.input_group_id do
|
||||
= render EditableChamp::EditableChampComponent.new champ:, form:
|
|
@ -1,25 +0,0 @@
|
|||
.autosave.autosave-state-idle{ data: { controller: 'autosave-status' } }
|
||||
%p.autosave-explanation.fr-text--sm
|
||||
%span.autosave-explanation-text
|
||||
- if dossier.brouillon?
|
||||
= t('views.users.dossiers.autosave.draft_explanation')
|
||||
- else
|
||||
= t('views.users.dossiers.autosave.explanation')
|
||||
= link_to t('views.users.dossiers.autosave.more_information'), t("links.common.faq.autosave_url"), class: 'autosave-more-infos fr-link fr-link--sm', **external_link_attributes
|
||||
|
||||
%p.autosave-status.succeeded
|
||||
%span.autosave-icon.icon.accept
|
||||
%span.autosave-label
|
||||
- if dossier.brouillon?
|
||||
= t('views.users.dossiers.autosave.draft_confirmation')
|
||||
- else
|
||||
= t('views.users.dossiers.autosave.confirmation')
|
||||
= link_to t('views.users.dossiers.autosave.more_information'), t("links.common.faq.autosave_url"), class: 'autosave-more-infos fr-link fr-link--sm', **external_link_attributes
|
||||
|
||||
%p.autosave-status.failed
|
||||
%span.autosave-icon ⚠️
|
||||
%span.autosave-label Impossible d’enregistrer le brouillon
|
||||
%button.button.small.autosave-retry{ type: :button, data: { action: 'autosave-status#onClickRetryButton', autosave_status_target: 'retryButton' } }
|
||||
%span.autosave-retry-label réessayer
|
||||
%span.autosave-retrying-label enregistrement en cours…
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
= render partial: "shared/dossiers/submit_is_over", locals: { dossier: dossier }
|
||||
|
||||
- if dossier.brouillon?
|
||||
- form_options = { url: brouillon_dossier_url(dossier), method: :patch, data: { save_on_input: true } }
|
||||
- form_options = { url: brouillon_dossier_url(dossier), method: :patch }
|
||||
- else
|
||||
- form_options = { url: modifier_dossier_url(dossier), method: :patch }
|
||||
= render Attachment::DeleteFormComponent.new
|
||||
|
@ -47,4 +47,4 @@
|
|||
= fields_for champ.input_name, champ do |form|
|
||||
= render EditableChamp::EditableChampComponent.new form: form, champ: champ
|
||||
|
||||
= render Dossiers::EditFooterComponent.new(dossier: dossier)
|
||||
= render Dossiers::EditFooterComponent.new(dossier: dossier, annotation: false)
|
||||
|
|
|
@ -7,9 +7,6 @@
|
|||
= fields_for champ.input_name, champ do |form|
|
||||
= render EditableChamp::EditableChampComponent.new form: form, champ: champ, seen_at: seen_at
|
||||
|
||||
- if !dossier.for_procedure_preview?
|
||||
.send-wrapper
|
||||
= f.submit 'Sauvegarder', class: 'button primary send', data: { disable: true }
|
||||
|
||||
= render Dossiers::EditFooterComponent.new(dossier: dossier, annotation: true)
|
||||
- else
|
||||
%h2.empty-text Aucune annotation privée
|
||||
|
|
|
@ -112,6 +112,7 @@ ignore_unused:
|
|||
- 'helpers.page_entries_info.*'
|
||||
- 'combo_search_component.result_slot_html.*'
|
||||
- 'combo_search_component.screen_reader_instructions'
|
||||
- 'links.common.*'
|
||||
# - '{devise,kaminari,will_paginate}.*'
|
||||
# - 'simple_form.{yes,no}'
|
||||
# - 'simple_form.{placeholders,hints,labels}.*'
|
||||
|
|
|
@ -281,12 +281,6 @@ en:
|
|||
users:
|
||||
dossiers:
|
||||
archived_dossier: "Your file will be kept %{duree_conservation_dossiers_dans_ds} more months"
|
||||
autosave:
|
||||
explanation: Your file is automatically saved.
|
||||
confirmation: File saved
|
||||
draft_explanation: Your draft is automatically saved.
|
||||
draft_confirmation: Draft saved
|
||||
more_information: More informations
|
||||
identite:
|
||||
identity_data: Identity data
|
||||
all_required: All fields are required.
|
||||
|
|
|
@ -277,12 +277,6 @@ fr:
|
|||
users:
|
||||
dossiers:
|
||||
archived_dossier: "Votre dossier sera conservé %{duree_conservation_dossiers_dans_ds} mois supplémentaire"
|
||||
autosave:
|
||||
explanation: Votre dossier est automatiquement enregistré.
|
||||
confirmation: Dossier enregistré
|
||||
draft_explanation: Votre brouillon est automatiquement enregistré.
|
||||
draft_confirmation: Brouillon enregistré
|
||||
more_information: En savoir plus
|
||||
identite:
|
||||
identity_data: Données d’identité
|
||||
all_required: Tous les champs sont obligatoires.
|
||||
|
|
|
@ -24,7 +24,7 @@ describe EditableChamp::EditableChampComponent, type: :component do
|
|||
context 'when a private champ' do
|
||||
let(:champ) { create(:champ, dossier: dossier, private: true) }
|
||||
|
||||
it { expect(subject).to eq('') }
|
||||
it { expect(subject).to eq('autosave') }
|
||||
end
|
||||
|
||||
context 'when a dossier is en_construction' do
|
||||
|
@ -41,7 +41,7 @@ describe EditableChamp::EditableChampComponent, type: :component do
|
|||
end
|
||||
|
||||
context 'when a private dropdown champ' do
|
||||
let(:controllers) { ['champ-dropdown'] }
|
||||
let(:controllers) { ['autosave', 'champ-dropdown'] }
|
||||
let(:champ) { create(:champ_drop_down_list, dossier: dossier, private: true) }
|
||||
|
||||
it { expect(subject).to eq(data) }
|
||||
|
@ -56,7 +56,7 @@ describe EditableChamp::EditableChampComponent, type: :component do
|
|||
end
|
||||
|
||||
context 'when a private dropdown champ' do
|
||||
let(:controllers) { ['champ-dropdown'] }
|
||||
let(:controllers) { ['autosave', 'champ-dropdown'] }
|
||||
let(:champ) { create(:champ_drop_down_list, dossier: dossier, private: true) }
|
||||
|
||||
it { expect(subject).to eq(data) }
|
||||
|
|
|
@ -771,7 +771,7 @@ describe Instructeurs::DossiersController, type: :controller do
|
|||
expect(controller.current_instructeur).to receive(:mark_tab_as_seen).with(dossier, :annotations_privees)
|
||||
another_instructeur.follow(dossier)
|
||||
Timecop.freeze(now)
|
||||
patch :update_annotations, params: params
|
||||
patch :update_annotations, params: params, format: :turbo_stream
|
||||
|
||||
champ_multiple_drop_down_list.reload
|
||||
champ_linked_drop_down_list.reload
|
||||
|
@ -819,7 +819,7 @@ describe Instructeurs::DossiersController, type: :controller do
|
|||
expect(champ_datetime.value).to eq('2019-12-21T13:17:00+01:00')
|
||||
expect(champ_repetition.champs.first.value).to eq('text')
|
||||
expect(dossier.reload.last_champ_private_updated_at).to eq(now)
|
||||
expect(response).to redirect_to(annotations_privees_instructeur_dossier_path(dossier.procedure, dossier))
|
||||
expect(response).to have_http_status(200)
|
||||
}
|
||||
|
||||
it 'updates the annotations' do
|
||||
|
@ -848,7 +848,7 @@ describe Instructeurs::DossiersController, type: :controller do
|
|||
|
||||
it {
|
||||
expect(dossier.reload.last_champ_private_updated_at).to eq(nil)
|
||||
expect(response).to redirect_to(annotations_privees_instructeur_dossier_path(dossier.procedure, dossier))
|
||||
expect(response).to have_http_status(200)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue