Merge branch 'main' into feature/prefill_repetible

This commit is contained in:
Damien Le Thiec 2023-01-31 16:39:00 +01:00
parent f91cc05d95
commit d7b01255fe
82 changed files with 764 additions and 563 deletions

View file

@ -635,7 +635,7 @@ GEM
nokogiri (>= 1.6.2)
rexml
xmlenc (>= 0.7.1)
sanitize (6.0.0)
sanitize (6.0.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
sanitize-url (0.1.4)

View file

@ -11,7 +11,6 @@
// The procedure description can still be read from the /commencer
// pages.
@media (max-width: $two-columns-breakpoint) {
.procedure-preview,
.agent-intro {
display: none;
}

View file

@ -9,6 +9,14 @@ $contact-padding: $default-space * 2;
padding-bottom: $contact-padding;
}
.recommandations {
p {
font-size: 1.5rem;
font-weight: bold;
line-height: 1.5rem;
}
}
ul {
margin-bottom: $default-space;
}

View file

@ -163,6 +163,12 @@
}
}
.drop_down_other { // scss-lint:disable SelectorFormat
label {
font-weight: normal;
}
}
input[type=text],
input[type=email],
input[type=password],

View file

@ -44,6 +44,10 @@
position: relative;
}
.dropdown-export .dropdown-content {
width: 450px;
}
.print-menu {
display: none;
position: absolute;

View file

@ -18,3 +18,9 @@
width: 9rem;
}
}
.fr-footer__top-link p {
margin-bottom: 0;
font-size: 0.75rem;
line-height: 1.25rem;
}

View file

@ -17,7 +17,8 @@ $procedure-description-line-height: 22px;
}
.simple {
font-size: 24px;
margin-bottom: 0.2rem;
font-size: 1.5rem;
color: $blue-france-500;
font-weight: bold;
}
@ -143,6 +144,14 @@ $procedure-description-line-height: 22px;
}
}
.no-procedure-presentation {
margin-bottom: 1.6rem;
p {
margin: 0;
}
}
.procedure-context-content {
@media (max-width: $procedure-context-breakpoint) {
input[type=submit] {

View file

@ -1,21 +1,21 @@
%span.dropdown{ data: { controller: 'menu-button' } }
%button.fr-btn.fr-btn--sm.dropdown-button{ data: { menu_button_target: 'button' }, class: @class_btn.present? ? @class_btn : 'fr-btn--secondary' }
- if @count.nil?
= t(".download_all")
= render Dropdown::MenuComponent.new(wrapper: :span, button_options: { class: ['fr-btn--sm', @class_btn.present? ? @class_btn : 'fr-btn--secondary']}, menu_options: { id: @count.nil? ? "download_menu" : "download_all_menu", class: ['dropdown-export'] }) do |menu|
- menu.with_button_inner_html do
= @count.nil? ? t(".download_all") : t(".download", count: @count)
- exports.each do |item|
- export = item[:export]
- if export.nil?
- menu.with_item do
= link_to download_export_path(export_format: item[:format]), role: 'menuitem', data: { turbo_method: :post, turbo: true } do
= t(".everything_#{item[:format]}_html")
- elsif export.available?
- menu.with_item do
%div
= link_to ready_link_label(export), export.file.service_url, target: "_blank", rel: "noopener", role: 'menuitem'
- if export.old?
= button_to download_export_path(export_format: export.format, force_export: true), refresh_button_options(export).merge(role: 'menuitem') do
.icon.retry
- else
= t(".download", count: @count)
.dropdown-content.fade-in-down{ style: 'width: 450px', data: { menu_button_target: 'menu' }, id: @count.nil? ? "download_menu" : "download_all_menu" }
%ul.dropdown-items{ 'data-turbo': 'true' }
- exports.each do |item|
- export = item[:export]
%li
- if export.nil?
= link_to t(".everything_#{item[:format]}_html"), download_export_path(export_format: item[:format]), data: { turbo_method: :post }
- elsif export.available?
= link_to ready_link_label(export), export.file.service_url, target: "_blank", rel: "noopener"
- if export.old?
= button_to download_export_path(export_format: export.format, force_export: true), **refresh_button_options(export) do
.icon.retry
- else
%span{ data: poll_controller_options(export) }
= pending_label(export)
- menu.with_item(aria: {disabled:"true"}, class: 'selected') do
%span{ data: poll_controller_options(export) }
= pending_label(export)

View file

@ -0,0 +1,50 @@
class Dropdown::MenuComponent < ApplicationComponent
renders_one :button_inner_html
# beware, items elements like button_to/link_to must include role: 'menuitem' for aria reason
renders_many :items, -> (options = {}, &block) do
tag.li(**options.merge(role: 'none'), &block)
end
renders_many :forms
def initialize(wrapper:,
wrapper_options: {},
button_options: {},
menu_options: {})
@wrapper = wrapper
@wrapper_options = wrapper_options
@button_options = button_options
@menu_options = menu_options
end
def wrapper_options
@wrapper_options.deep_merge({
class: wrapper_class_names,
data: { controller: 'menu-button' }
})
end
def wrapper_class_names
['dropdown'] + Array(@wrapper_options[:class])
end
def button_id
"#{menu_id}_button"
end
def menu_id
@menu_options[:id] ||= SecureRandom.uuid
@menu_options[:id]
end
def menu_role
forms? ? :region : :menu
end
def menu_class_names
['dropdown-content'] + Array(@menu_options[:class])
end
def button_class_names
['fr-btn', 'dropdown-button'] + Array(@button_options[:class])
end
end

View file

@ -0,0 +1,15 @@
= content_tag(@wrapper, wrapper_options) do
%button{ class: button_class_names, id: button_id, data: { menu_button_target: 'button' }, "aria-expanded": "false", 'aria-haspopup': 'true', 'aria-controls': menu_id }
= button_inner_html
%div{ data: { menu_button_target: 'menu' }, id: menu_id, 'aria-labelledby': button_id, role: menu_role, 'tab-index': -1, class: menu_class_names }
-# the dropdown can be a menu with a list of item
- if items?
%ul.dropdown-items.fr-pl-0{ role: 'none' }
- items.each do |dropdown_item|
= dropdown_item
-# the dropdown can be a menu with forms
- if forms?
- forms.each do |form|
= form

View file

@ -1,4 +1,4 @@
.drop_down_other{ class: @champ.other_value_present? ? '' : 'hidden' }
.notice
%p Veuillez saisir votre autre choix
= @form.text_field :value_other, maxlength: 200, size: nil, disabled: !@champ.other_value_present?
%label{ for: dom_id(@champ, :value_other) } Veuillez saisir votre autre choix
= @form.text_field :value_other, maxlength: 200, size: nil, id: dom_id(@champ, :value_other), disabled: !@champ.other_value_present?

View file

@ -8,6 +8,7 @@ module Administrateurs
def edit
@attestation_template = build_attestation_template
@attestation_template.validate
end
def update

View file

@ -140,7 +140,7 @@ module Experts
end
def telecharger_pjs
files = ActiveStorage::DownloadableFile.create_list_from_dossiers(Dossier.where(id: @dossier.id), true)
files = ActiveStorage::DownloadableFile.create_list_from_dossiers(Dossier.where(id: @dossier.id))
cleaned_files = ActiveStorage::DownloadableFile.cleanup_list_from_dossier(files)
zipline(cleaned_files, "dossier-#{@dossier.id}.zip")

View file

@ -235,7 +235,7 @@ module Instructeurs
end
def telecharger_pjs
files = ActiveStorage::DownloadableFile.create_list_from_dossiers(Dossier.where(id: dossier.id), true)
files = ActiveStorage::DownloadableFile.create_list_from_dossiers(Dossier.where(id: dossier.id), with_champs_private: true, include_infos_administration: true)
cleaned_files = ActiveStorage::DownloadableFile.cleanup_list_from_dossier(files)
zipline(cleaned_files, "dossier-#{dossier.id}.zip")

View file

@ -26,11 +26,7 @@ module Users
@dossiers_invites = current_user.dossiers_invites.merge(dossiers_visibles)
@dossiers_supprimes_recemment = current_user.dossiers.hidden_by_user.merge(dossiers)
@dossiers_supprimes_definitivement = current_user.deleted_dossiers.order_by_updated_at.page(page)
@dossier_transfers = DossierTransfer
.includes(dossiers: :user)
.with_dossiers
.where(email: current_user.email)
.page(page)
@dossier_transfers = DossierTransfer.for_email(current_user.email).page(page)
@statut = statut(@user_dossiers, @dossiers_traites, @dossiers_invites, @dossiers_supprimes_recemment, @dossiers_supprimes_definitivement, @dossier_transfers, @dossiers_close_to_expiration, params[:statut])
end

View file

@ -17,8 +17,11 @@ export function useDeferredSubmit(input?: HTMLInputElement): {
runCallback();
if (
!Array.from(form.elements).some((e) =>
e.hasAttribute('data-direct-upload-url')
!Array.from(form.elements).some(
(e) =>
e.hasAttribute('data-direct-upload-url') &&
'value' in e &&
e.value != ''
)
) {
form.submit();

View file

@ -65,7 +65,10 @@ export class ApplicationController extends Controller {
FOCUS_EVENTS.includes(targetOrEventName)
);
} else {
invariant(eventNameOrHandler == 'string', 'event name is required');
invariant(
typeof eventNameOrHandler == 'string',
'event name is required'
);
invariant(handler, 'handler is required');
this.onTarget(targetOrEventName, eventNameOrHandler, handler);
}

View file

@ -89,9 +89,12 @@ export class AutosaveController extends ApplicationController {
isCheckboxOrRadioInputElement(target) ||
(!this.saveOnInput && isTextInputElement(target))
) {
this.enqueueAutosaveRequest();
this.showConditionnalSpinner(target);
// Wait next tick so champs having JS can interact
// with form elements before extracting form data.
setTimeout(() => {
this.enqueueAutosaveRequest();
this.showConditionnalSpinner(target);
}, 0);
}
}
}

View file

@ -34,6 +34,7 @@ export class ChampDropdownController extends ApplicationController {
if (target.value == '__other__') {
show(inputGroup);
input.disabled = false;
input.focus();
} else {
hide(inputGroup);
input.disabled = true;

View file

@ -6,53 +6,26 @@ export class MenuButtonController extends ApplicationController {
declare readonly buttonTarget: HTMLButtonElement;
declare readonly menuTarget: HTMLElement;
#teardown?: () => void;
connect() {
this.setup();
}
disconnect(): void {
this.#teardown?.();
}
private get isOpen() {
return (this.element as HTMLElement).classList.contains('open');
}
private get isMenu() {
return !(this.element as HTMLElement).dataset.popover;
return this.menuTarget.getAttribute('role') == 'menu';
}
private setup() {
this.buttonTarget.setAttribute(
'aria-haspopup',
this.isMenu ? 'menu' : 'true'
);
this.buttonTarget.setAttribute('aria-controls', this.menuTarget.id);
if (!this.buttonTarget.id) {
this.buttonTarget.id = `${this.menuTarget.id}_button`;
}
this.menuTarget.setAttribute('aria-labelledby', this.buttonTarget.id);
this.menuTarget.setAttribute('role', this.isMenu ? 'menu' : 'region');
// see:
// To progressively enhance this navigation widget that is by default accessible,
// the class to hide the menu and the inclusion of tabindex="-1" on the interactive menuitem
// content should be added with JavaScript on load.
this.menuTarget.classList.add('fade-in-down');
this.menuTarget.setAttribute('tab-index', '-1');
if (this.isMenu) {
for (const menuItem of this.menuTarget.querySelectorAll('a')) {
menuItem.setAttribute('role', 'menuitem');
}
for (const dropdownItems of this.menuTarget.querySelectorAll(
'.dropdown-items'
)) {
dropdownItems.setAttribute('role', 'none');
}
for (const dropdownItems of this.menuTarget.querySelectorAll(
'.dropdown-items > li'
)) {
dropdownItems.setAttribute('role', 'none');
}
this.menuItems.map((menuItem) => menuItem.setAttribute('tabindex', '-1'));
}
this.on('click', (event) => {
@ -78,6 +51,14 @@ export class MenuButtonController extends ApplicationController {
this.onMenuKeydown(event);
}
});
this.on(document.body, 'click', (event) => {
const target = event.target as HTMLElement;
if (this.isOpen && this.isClickOutside(target)) {
this.menuTarget.classList.remove('fade-in-down');
this.close();
}
});
}
private open(focusMenuItem: 'first' | 'last' = 'first') {
@ -85,30 +66,18 @@ export class MenuButtonController extends ApplicationController {
this.menuTarget.parentElement?.classList.add('open');
this.menuTarget.focus();
const onClickBody = (event: Event) => {
const target = event.target as HTMLElement;
if (this.isClickOutside(target)) {
this.menuTarget.classList.remove('fade-in-down');
this.close();
}
};
requestAnimationFrame(() => {
if (focusMenuItem == 'first') {
this.setFocusToFirstMenuitem();
} else {
this.setFocusToLastMenuitem();
}
document.body.addEventListener('click', onClickBody);
});
this.#teardown = () =>
document.body.removeEventListener('click', onClickBody);
}
private close() {
this.buttonTarget.removeAttribute('aria-expanded');
this.buttonTarget.setAttribute('aria-expanded', 'false');
this.menuTarget.parentElement?.classList.remove('open');
this.#teardown?.();
this.setFocusToMenuitem(null);
}

View file

@ -1,7 +1,7 @@
class ActiveStorage::DownloadableFile
def self.create_list_from_dossiers(dossiers, for_expert = false)
PiecesJustificativesService.generate_dossier_export(dossiers, include_infos_administration: !for_expert) +
PiecesJustificativesService.liste_documents(dossiers, for_expert)
def self.create_list_from_dossiers(dossiers, with_bills: false, with_champs_private: false, include_infos_administration: false)
PiecesJustificativesService.generate_dossier_export(dossiers, include_infos_administration:) +
PiecesJustificativesService.liste_documents(dossiers, with_bills:, with_champs_private:)
end
def self.cleanup_list_from_dossier(files)

View file

@ -8,6 +8,7 @@
# confidentiel :boolean default(FALSE), not null
# email :string
# introduction :text
# reminded_at :datetime
# revoked_at :datetime
# created_at :datetime not null
# updated_at :datetime not null

View file

@ -226,7 +226,7 @@ class Champ < ApplicationRecord
private
def html_id
"#{stable_id}-#{id}"
"champ-#{stable_id}-#{id}"
end
def needs_dossier_id?
@ -238,7 +238,7 @@ class Champ < ApplicationRecord
end
def cleanup_if_empty
if external_id_changed?
if persisted? && external_id_changed?
self.data = nil
end
end

View file

@ -39,7 +39,7 @@ class Champs::DateChamp < Champ
private
def convert_to_iso8601
return if valid_iso8601?
return if likely_iso8601_format? && parsable_iso8601?
self.value = if /^\d{2}\/\d{2}\/\d{4}$/.match?(value)
Date.parse(value).iso8601
@ -49,12 +49,20 @@ class Champs::DateChamp < Champ
end
def iso_8601
return if valid_iso8601? || value.blank?
return if parsable_iso8601? || value.blank?
# i18n-tasks-use t('errors.messages.not_a_date')
errors.add :date, errors.generate_message(:value, :not_a_date)
end
def valid_iso8601?
def likely_iso8601_format?
/^\d{4}-\d{2}-\d{2}$/.match?(value)
end
def parsable_iso8601?
Date.parse(value)
true
rescue ArgumentError, # case 2023-27-02, out of range
TypeError # nil
false
end
end

View file

@ -54,32 +54,32 @@ module DossierRebaseConcern
.group_by(&:op)
.tap { _1.default = [] }
champs_by_stable_id = champs
.joins(:type_de_champ)
.group_by(&:stable_id)
.transform_values { Champ.where(id: _1) }
# add champ
changes_by_op[:add]
.map(&:stable_id)
.map { target_coordinates_by_stable_id[_1] }
.each { add_new_champs_for_revision(_1) }
.each { add_new_champs_for_revision(target_coordinates_by_stable_id[_1.stable_id]) }
# remove champ
changes_by_op[:remove]
.each { delete_champs_for_revision(_1.stable_id) }
.each { champs_by_stable_id[_1.stable_id].destroy_all }
if brouillon?
changes_by_op[:update]
.map { |change| [change, champs.joins(:type_de_champ).where(type_de_champ: { stable_id: change.stable_id })] }
.each { |change, champs| apply(change, champs) }
.each { apply(_1, champs_by_stable_id[_1.stable_id]) }
end
# due to repetition tdc clone on update or erase
# we must reassign tdc to the latest version
Champ
.includes(:type_de_champ)
.where(dossier: self)
.map { [_1, target_coordinates_by_stable_id[_1.stable_id].type_de_champ] }
.each { |champ, target_tdc| champ.update_columns(type_de_champ_id: target_tdc.id, rebased_at: Time.zone.now) }
champs_by_stable_id
.filter_map { |stable_id, champs| [target_coordinates_by_stable_id[stable_id].type_de_champ_id, champs] if champs.present? }
.each { |type_de_champ_id, champs| champs.update_all(type_de_champ_id:) }
# update dossier revision
self.update_column(:revision_id, target_revision.id)
update_column(:revision_id, target_revision.id)
end
def apply(change, champs)
@ -92,18 +92,27 @@ module DossierRebaseConcern
value: nil,
value_json: nil,
external_id: nil,
data: nil)
data: nil,
rebased_at: Time.zone.now)
when :drop_down_options
# we are removing options, we need to remove the value if it contains one of the removed options
removed_options = change.from - change.to
if removed_options.present? && champs.any? { _1.in?(removed_options) }
champs.filter { _1.in?(removed_options) }.each { _1.remove_option(removed_options) }
champs.filter { _1.in?(removed_options) }.each do
_1.remove_option(removed_options)
_1.update_column(:rebased_at, Time.zone.now)
end
end
when :carte_layers
# if we are removing cadastres layer, we need to remove cadastre geo areas
if change.from.include?(:cadastres) && !change.to.include?(:cadastres)
champs.each { _1.cadastres.each(&:destroy) }
champs.filter { _1.cadastres.present? }.each do
_1.cadastres.each(&:destroy)
_1.update_column(:rebased_at, Time.zone.now)
end
end
else
champs.update_all(rebased_at: Time.zone.now)
end
end
@ -126,20 +135,13 @@ module DossierRebaseConcern
end
def create_champ(target_coordinate, parent, row_id: nil)
params = { revision: target_coordinate.revision, row_id: }.compact
params = { revision: target_coordinate.revision, rebased_at: Time.zone.now, row_id: }.compact
champ = target_coordinate
.type_de_champ
.build_champ(params)
parent.champs << champ
end
def delete_champs_for_revision(stable_id)
champs
.joins(:type_de_champ)
.where(types_de_champ: { stable_id: })
.destroy_all
end
def purge_piece_justificative_file(champ)
ActiveStorage::Attachment.where(id: champ.piece_justificative_file.ids).delete_all
end

View file

@ -1184,7 +1184,7 @@ class Dossier < ApplicationRecord
end
def geo_data?
geo_areas.present?
GeoArea.exists?(champ_id: champs_public.ids + champs_private.ids)
end
def to_feature_collection

View file

@ -8,15 +8,18 @@
# updated_at :datetime not null
#
class DossierTransfer < ApplicationRecord
include EmailSanitizableConcern
has_many :dossiers, dependent: :nullify
EXPIRATION_LIMIT = 2.weeks
validates :email, format: { with: Devise.email_regexp }
before_validation -> { sanitize_email(:email) }
scope :pending, -> { where('created_at > ?', (Time.zone.now - EXPIRATION_LIMIT)) }
scope :stale, -> { where('created_at < ?', (Time.zone.now - EXPIRATION_LIMIT)) }
scope :with_dossiers, -> { joins(:dossiers).merge(Dossier.visible_by_user) }
scope :for_email, -> (email) { includes(dossiers: :user).with_dossiers.where(email: email) }
after_create_commit :send_notification

View file

@ -330,8 +330,11 @@ class ProcedurePresentation < ApplicationRecord
if field['scope'].present?
I18n.t(field['scope']).map(&:to_a).map(&:reverse)
elsif field['table'] == 'groupe_instructeur'
instructeur.groupe_instructeurs
.map { [_1.label, _1.id] }
instructeur.groupe_instructeurs.filter_map do
if _1.procedure_id == procedure.id
[_1.label, _1.id]
end
end
end
end

View file

@ -243,7 +243,7 @@ class TypeDeChamp < ApplicationRecord
end
def only_present_on_draft?
revisions.size == 1
revisions.one? && revisions.first.draft?
end
def drop_down_other?

View file

@ -1,13 +1,13 @@
class PiecesJustificativesService
def self.liste_documents(dossiers, for_expert)
def self.liste_documents(dossiers, with_bills:, with_champs_private:)
bill_ids = []
docs = dossiers.in_batches.flat_map do |batch|
pjs = pjs_for_champs(batch, for_expert) +
pjs = pjs_for_champs(batch, with_champs_private:) +
pjs_for_commentaires(batch) +
pjs_for_dossier(batch)
if !for_expert
if with_bills
# some bills are shared among operations
# so first, all the bill_ids are fetched
operation_logs, some_bill_ids = operation_logs_and_signature_ids(batch)
@ -19,7 +19,7 @@ class PiecesJustificativesService
pjs
end
if !for_expert
if with_bills
# then the bills are retrieved without duplication
docs += signatures(bill_ids.uniq)
end
@ -117,12 +117,12 @@ class PiecesJustificativesService
private
def self.pjs_for_champs(dossiers, for_expert = false)
def self.pjs_for_champs(dossiers, with_champs_private:)
champs = Champ
.joins(:piece_justificative_file_attachments)
.where(type: "Champs::PieceJustificativeChamp", dossier: dossiers)
if for_expert
if !with_champs_private
champs = champs.where(private: false)
end

View file

@ -15,7 +15,7 @@ class ProcedureArchiveService
dossiers.processed_in_month(archive.month)
end
attachments = ActiveStorage::DownloadableFile.create_list_from_dossiers(dossiers)
attachments = ActiveStorage::DownloadableFile.create_list_from_dossiers(dossiers, with_bills: true, with_champs_private: true)
DownloadableFileService.download_and_zip(@procedure, attachments, zip_root_folder(archive)) do |zip_filepath|
ArchiveUploader.new(procedure: @procedure, filename: archive.filename(@procedure), filepath: zip_filepath)

View file

@ -35,7 +35,7 @@ class ProcedureExportService
end
def to_zip
attachments = ActiveStorage::DownloadableFile.create_list_from_dossiers(dossiers, true)
attachments = ActiveStorage::DownloadableFile.create_list_from_dossiers(dossiers, with_champs_private: true)
DownloadableFileService.download_and_zip(procedure, attachments, base_filename) do |zip_filepath|
ArchiveUploader.new(procedure: procedure, filename: filename(:zip), filepath: zip_filepath).blob

View file

@ -41,42 +41,43 @@
%li
= link_to admin_procedure_path(procedure), class: 'fr-btn fr-icon-draft-line fr-btn--tertiary' do
Modifier
%li.dropdown{ data: { controller: 'menu-button' } }
%button.fr-btn.fr-btn--tertiary.dropdown-button.procedures-actions-btn{ data: { menu_button_target: 'button' } }
= render Dropdown::MenuComponent.new(wrapper: :li, button_options: { class: ['fr-btn--tertiary'] }, menu_options: { id: dom_id(procedure, :actions_menu)}) do |menu|
- menu.with_button_inner_html do
Actions
.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' }, id: dom_id(procedure, :actions_menu) }
%ul.dropdown-items.pl-0
- if !procedure.close? && !procedure.discarded?
%li
= link_to sanitize_url(procedure.brouillon? ? commencer_test_url(path: procedure.path) : commencer_url(path: procedure.path)), target: :blank, rel: :noopener do
%span.icon.in-progress
.dropdown-description
%h4= t('administrateurs.dropdown_actions.to_test')
- unless procedure.discarded?
%li
= link_to admin_procedure_clone_path(procedure.id), class: 'clone-btn', data: { method: :put } do
%span.icon.new-folder
.dropdown-description
%h4= t('administrateurs.dropdown_actions.to_clone')
- if !procedure.close? && !procedure.discarded?
- menu.with_item do
= link_to(sanitize_url(procedure.brouillon? ? commencer_test_url(path: procedure.path) : commencer_url(path: procedure.path)), target: :blank, rel: :noopener, role: 'menuitem') do
%span.icon.in-progress
.dropdown-description
%h4= t('administrateurs.dropdown_actions.to_test')
- if procedure.publiee?
%li
= link_to admin_procedure_close_path(procedure_id: procedure.id) do
%span.icon.archive
.dropdown-description
%h4= t('administrateurs.dropdown_actions.to_close')
- if !procedure.discarded?
- menu.with_item do
= link_to(admin_procedure_clone_path(procedure.id), role: 'menuitem', class: 'clone-btn', data: { method: :put }) do
%span.icon.new-folder
.dropdown-description
%h4= t('administrateurs.dropdown_actions.to_clone')
- if procedure.can_be_deleted_by_administrateur? && !procedure.discarded?
%li
= link_to admin_procedure_path(procedure), method: :delete, data: { confirm: "Voulez-vous vraiment supprimer la démarche ? \nToute suppression est définitive et s'appliquera aux éventuels autres administrateurs de cette démarche !" } do
%span.icon.refuse
.dropdown-description
%h4= t('administrateurs.dropdown_actions.delete')
- if procedure.publiee?
- menu.with_item do
= link_to(admin_procedure_close_path(procedure_id: procedure.id), role: 'menuitem') do
%span.icon.archive
.dropdown-description
%h4= t('administrateurs.dropdown_actions.to_close')
- if procedure.can_be_deleted_by_administrateur? && !procedure.discarded?
- menu.with_item do
= link_to admin_procedure_path(procedure), role: 'menuitem', method: :delete, data: { confirm: "Voulez-vous vraiment supprimer la démarche ? \nToute suppression est définitive et s'appliquera aux éventuels autres administrateurs de cette démarche !" } do
%span.icon.refuse
.dropdown-description
%h4= t('administrateurs.dropdown_actions.delete')
- if procedure.discarded?
- menu.with_item do
= link_to restore_admin_procedure_path(procedure), role: 'menuitem', method: :put do
%span.icon.unarchive
.dropdown-description
%h4= t('administrateurs.dropdown_actions.restore')
- if procedure.discarded?
%li
= link_to restore_admin_procedure_path(procedure), method: :put do
%span.icon.unarchive
.dropdown-description
%h4= t('administrateurs.dropdown_actions.restore')

View file

@ -9,11 +9,11 @@
%li= link_to("Dossier nº #{dossier.id}", expert_avis_path(avis.procedure, avis))
.header-actions
%span.dropdown.print-menu-opener{ data: { controller: 'menu-button' } }
%button.button.dropdown-button.icon-only{ data: { menu_button_target: 'button' } }
%span.icon.attached
%ul.print-menu.dropdown-content#print-pj-menu{ data: { menu_button_target: 'menu' } }
%li= link_to "Télécharger le dossier et toutes ses pièces jointes", telecharger_pjs_expert_avis_path(avis.procedure, avis), target: "_blank", rel: "noopener", class: "menu-item menu-link"
.fr-download
= link_to telecharger_pjs_expert_avis_path(avis.procedure, avis), download: :download, class: "menu-item menu-link fr-download__link" do
Télécharger le dossier et toutes ses pièces jointes
%span.fr-download__detail
ZIP
%nav.tabs
%ul

View file

@ -1,21 +1,31 @@
%ul.fr-btns-group.fr-btns-group--sm.fr-btns-group--inline-md.fr-btns-group--icon-right
%li.dropdown.print-menu-opener{ data: { controller: 'menu-button' } }
%button.fr-btn.fr-btn--tertiary.fr-icon-printer-line.dropdown-button{ title: 'imprimer', 'aria-label': 'Imprimer', data: { menu_button_target: 'button' } } Imprimer
%ul#print-menu.print-menu.dropdown-content{ data: { menu_button_target: 'menu' } }
%li
= link_to "Tout le dossier", print_instructeur_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link"
%li
= link_to "Uniquement cet onglet", "#", onclick: "window.print()", class: "menu-item menu-link"
%li
= link_to "Export PDF", instructeur_dossier_path(dossier.procedure, dossier, format: :pdf), target: "_blank", rel: "noopener", class: "menu-item menu-link"
- if dossier.geo_data?
%li
= link_to "Export GeoJSON", geo_data_instructeur_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link"
= render Dropdown::MenuComponent.new(wrapper: :li, menu_options: { id: 'print-menu'}, button_options: { class: ['fr-btn--tertiary', 'fr-icon-printer-line']}) do |menu|
- menu.with_button_inner_html do
Imprimer
%li.dropdown.print-menu-opener{ data: { controller: 'menu-button' } }
%button.fr-btn.fr-btn--tertiary.fr-icon-download-line.dropdown-button{ data: { menu_button_target: 'button', 'aria-label': 'Télécharger' } } Télécharger
%ul#print-pj-menu.print-menu.dropdown-content{ data: { menu_button_target: 'menu' } }
%li= link_to "Télécharger le dossier et toutes ses pièces jointes", telecharger_pjs_instructeur_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link"
- menu.with_item do
= link_to print_instructeur_dossier_path(dossier.procedure, dossier), role: 'menuitem', target: "_blank", rel: "noopener", class: "menu-item menu-link" do
Tout le dossier
- menu.with_item do
= link_to '#', role: 'menuitem', onclick: "window.print()", class: "menu-item menu-link" do
Uniquement cet onglet
- menu.with_item do
= link_to instructeur_dossier_path(dossier.procedure, dossier, format: :pdf), target: "_blank", rel: "noopener", class: "menu-item menu-link", role: 'menuitem' do
Export PDF
- if dossier.geo_data?
- menu.with_item do
= link_to geo_data_instructeur_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link", role: 'menuitem' do
Export GeoJSON
= render Dropdown::MenuComponent.new(wrapper: :li, menu_options: { id: 'print-pj-menu'}, button_options: { class: ['fr-btn--tertiary', 'fr-icon-download-line']}) do |menu|
- menu.with_button_inner_html do
Télécharger
- menu.with_item do
= link_to telecharger_pjs_instructeur_dossier_path(dossier.procedure, dossier), target: "_blank", rel: "noopener", class: "menu-item menu-link", role: 'menuitem' do
Télécharger le dossier et toutes ses pièces jointes
%li
= render partial: "instructeurs/procedures/dossier_actions",

View file

@ -1,120 +1,107 @@
.dropdown{ data: { controller: 'menu-button', popover: 'true', turbo_force: true } }
-# Dropdown button title
%button.fr-btn.dropdown-button{ class: button_or_label_class(dossier), data: { menu_button_target: 'button' } }
= dossier_display_state dossier
= render Dropdown::MenuComponent.new(wrapper: :div, wrapper_options: { data: {'turbo-force': true} }, button_options: { class: [button_or_label_class(dossier)] }) do |menu|
- menu.with_button_inner_html do
= dossier_display_state(dossier)
-# Dropdown content
#state-menu.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } }
- if dossier.en_construction?
- menu.with_item(aria: {disabled:"true"}, class: 'selected') do
%span.icon.edit
.dropdown-description
%h4 En construction
Vous permettez à l'usager de modifier ses réponses au formulaire
- if dossier.en_construction?
-# ------------------------------------------------------
-# EN CONSTRUCTION
-# ------------------------------------------------------
%ul.dropdown-items
- menu.with_item('data-turbo': 'true') do
= link_to(passer_en_instruction_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo_method: :post, turbo_confirm: "Confirmez-vous le passage en instruction de ce dossier ?", turbo: true }, role: 'menuitem') do
%span.icon.in-progress
.dropdown-description
%h4 Passer en instruction
Lusager ne pourra plus modifier le formulaire
%li.selected
%span.icon.edit
- elsif dossier.en_instruction?
- menu.with_item('data-turbo': 'true') do
= link_to(repasser_en_construction_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo_method: :post, turbo_confirm: "Confirmez-vous le passage en construction de ce dossier ?"}, role: 'menuitem') do
%span.icon.edit
.dropdown-description
%h4 Repasser en construction
Vous permettrez à l'usager de modifier ses réponses au formulaire
- menu.with_item(aria: {disabled:"true"}, class: 'selected') do
%span.icon.in-progress
.dropdown-description
%h4 En instruction
Lusager ne peut modifier son dossier pendant l'instruction
- menu.with_item do
= link_to('#', onclick: "DS.showMotivation(event, 'accept');", role: 'menuitem') do
%span.icon.accept
.dropdown-description
%h4 Accepter
Lusager sera notifié que son dossier a été accepté
- menu.with_item do
= link_to('#', onclick: "DS.showMotivation(event, 'without-continuation');", role: 'menuitem') do
%span.icon.without-continuation
.dropdown-description
%h4 Classer sans suite
Lusager sera notifié que son dossier a été classé sans suite
- menu.with_item do
= link_to('#', onclick: "DS.showMotivation(event, 'refuse');", role: 'menuitem') do
%span.icon.refuse
.dropdown-description
%h4 Refuser
Lusager sera notifié que son dossier a été refusé
- menu.with_form do
= render partial: 'instructeurs/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Accepter le dossier', placeholder: 'Expliquez au demandeur pourquoi ce dossier est accepté (facultatif)', popup_class: 'accept', process_action: 'accepter', title: 'Accepter', confirm: "Confirmez-vous l'acceptation ce dossier ?" }
- menu.with_form do
= render partial: 'instructeurs/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Classer le dossier sans suite', placeholder: 'Expliquez au demandeur pourquoi ce dossier est classé sans suite (obligatoire)', popup_class: 'without-continuation', process_action: 'classer_sans_suite', title: 'Classer sans suite', confirm: 'Confirmez-vous le classement sans suite de ce dossier ?' }
- menu.with_form do
= render partial: 'instructeurs/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Refuser le dossier', placeholder: 'Expliquez au demandeur pourquoi ce dossier est refusé (obligatoire)', popup_class: 'refuse', process_action: 'refuser', title: 'Refuser', confirm: 'Confirmez-vous le refus de ce dossier ?' }
- elsif dossier.termine?
- if dossier.motivation.present?
- menu.with_item(class: 'inactive') do
%span.icon.info
.dropdown-description
%h4 Motivation
%p « #{dossier.motivation} »
- if dossier.justificatif_motivation.attached?
- menu.with_item(class: 'inactive') do
%span.icon.justificatif
.dropdown-description
%h4 Justificatif
%p Ce justificatif joint par linstructeur a été envoyé au demandeur.
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
- if dossier.attestation.present?
- menu.with_item do
= link_to(attestation_instructeur_dossier_path(dossier.procedure, dossier), target: '_blank', rel: 'noopener', role: 'menuitem') do
%span.icon.preview
.dropdown-description
%h4 En construction
Vous permettez à l'usager de modifier ses réponses au formulaire
%h4 Voir lattestation
%p Cette attestation a été envoyée automatiquement au demandeur.
%li{ 'data-turbo': 'true' }
= link_to passer_en_instruction_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo_method: :post, turbo_confirm: "Confirmez-vous le passage en instruction de ce dossier ?" } do
%span.icon.in-progress
.dropdown-description
%h4 Passer en instruction
Lusager ne pourra plus modifier le formulaire
- elsif dossier.en_instruction?
-# ------------------------------------------------------
-# EN INSTRUCTION
-# ------------------------------------------------------
%ul.dropdown-items
%li{ 'data-turbo': 'true' }
= link_to repasser_en_construction_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo_method: :post, turbo_confirm: "Confirmez-vous le passage en construction de ce dossier ?" } do
%span.icon.edit
.dropdown-description
%h4 Repasser en construction
Vous permettrez à l'usager de modifier ses réponses au formulaire
%li.selected
- if dossier.can_repasser_en_instruction?
- menu.with_item do
= link_to(repasser_en_instruction_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo_method: :post, turbo_confirm: "Voulez vous remettre le dossier #{dossier.id} en instruction ?", turbo: true }, role: 'menuitem') do
%span.icon.in-progress
.dropdown-description
%h4 En instruction
Lusager ne peut modifier son dossier pendant l'instruction
%h4 Repasser en instruction
Lusager sera notifié que son dossier est réexaminé.
- elsif dossier.user_deleted?
- menu.with_item do
%span.icon.info
.dropdown-description
%h4 En attente darchivage
Lusager a supprimé son compte. Vous pouvez archiver puis supprimer le dossier.
%li
%a{ href: '#', onclick: "DS.showMotivation(event, 'accept');" }
%span.icon.accept
.dropdown-description
%h4 Accepter
Lusager sera notifié que son dossier a été accepté
%li
%a{ href: '#', onclick: "DS.showMotivation(event, 'without-continuation');" }
%span.icon.without-continuation
.dropdown-description
%h4 Classer sans suite
Lusager sera notifié que son dossier a été classé sans suite
%li
%a{ href: '#', onclick: "DS.showMotivation(event, 'refuse');" }
%span.icon.refuse
.dropdown-description
%h4 Refuser
Lusager sera notifié que son dossier a été refusé
= render partial: 'instructeurs/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Accepter le dossier', placeholder: 'Expliquez au demandeur pourquoi ce dossier est accepté (facultatif)', popup_class: 'accept', process_action: 'accepter', title: 'Accepter', confirm: "Confirmez-vous l'acceptation ce dossier ?" }
= render partial: 'instructeurs/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Classer le dossier sans suite', placeholder: 'Expliquez au demandeur pourquoi ce dossier est classé sans suite (obligatoire)', popup_class: 'without-continuation', process_action: 'classer_sans_suite', title: 'Classer sans suite', confirm: 'Confirmez-vous le classement sans suite de ce dossier ?' }
= render partial: 'instructeurs/dossiers/state_button_motivation', locals: { dossier: dossier, popup_title: 'Refuser le dossier', placeholder: 'Expliquez au demandeur pourquoi ce dossier est refusé (obligatoire)', popup_class: 'refuse', process_action: 'refuser', title: 'Refuser', confirm: 'Confirmez-vous le refus de ce dossier ?' }
- elsif dossier.termine?
-# ---------------------------------------------------
-# TERMINÉ
-# ---------------------------------------------------
%ul.dropdown-items
- if dossier.motivation.present?
%li.inactive
%span.icon.info
.dropdown-description
%h4 Motivation
%p « #{dossier.motivation} »
- if dossier.justificatif_motivation.attached?
%li.inactive
%span.icon.justificatif
.dropdown-description
%h4 Justificatif
%p Ce justificatif joint par linstructeur a été envoyé au demandeur.
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
- if dossier.attestation.present?
%li
= link_to attestation_instructeur_dossier_path(dossier.procedure, dossier), target: '_blank', rel: 'noopener' do
%span.icon.preview
.dropdown-description
%h4 Voir lattestation
%p Cette attestation a été envoyée automatiquement au demandeur.
- if dossier.can_repasser_en_instruction?
%li{ 'data-turbo': 'true' }
= link_to repasser_en_instruction_instructeur_dossier_path(dossier.procedure, dossier), data: { turbo_method: :post, turbo_confirm: "Voulez vous remettre le dossier #{dossier.id} en instruction ?" } do
%span.icon.in-progress
.dropdown-description
%h4 Repasser en instruction
Lusager sera notifié que son dossier est réexaminé.
- elsif dossier.user_deleted?
%li
%span.icon.info
.dropdown-description
%h4 En attente darchivage
Lusager a supprimé son compte. Vous pouvez archiver puis supprimer le dossier.
%li
= link_to instructeur_dossier_path(dossier.procedure, dossier), method: :delete do
%span.icon.delete
.dropdown-description
%h4 Supprimer le dossier
- menu.with_item do
= link_to(instructeur_dossier_path(dossier.procedure, dossier), method: :delete, role: 'menuitem') do
%span.icon.delete
.dropdown-description
%h4 Supprimer le dossier

View file

@ -2,33 +2,36 @@
= link_to restore_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: "fr-btn fr-btn--secondary" do
= t('views.instructeurs.dossiers.restore')
- elsif close_to_expiration || Dossier::TERMINE.include?(state)
%li.dropdown.user-dossier-actions{ data: { controller: 'menu-button' } }
%button.fr-btn.fr-mb-0.dropdown-button{ data: { menu_button_target: 'button' } }
= render Dropdown::MenuComponent.new(wrapper: :li, button_options: { class: ['fr-mb-0']}, menu_options: { id: "dossier_#{dossier_id}_actions_menu", class: 'user-dossier-actions' }) do |menu|
- menu.with_button_inner_html do
Actions
.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' }, id: "dossier_#{dossier_id}_actions_menu" }
%ul.dropdown-items
- if close_to_expiration
%li
= link_to repousser_expiration_instructeur_dossier_path(procedure_id, dossier_id), method: :post do
%span.icon.standby
%span.dropdown-description= t('instructeurs.dossiers.header.banner.button_delay_expiration')
- if archived
%li
= link_to unarchive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch do
%span.icon.unarchive
%span.dropdown-description
Désarchiver le dossier
- else
%li
= link_to archive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch do
%span.icon.archive
%span.dropdown-description
Archiver le dossier
%li.danger
= link_to instructeur_dossier_path(procedure_id, dossier_id), method: :delete do
%span.icon.delete
%span.dropdown-description
= t('views.instructeurs.dossiers.delete_dossier')
- if close_to_expiration
- menu.with_item do
= link_to(repousser_expiration_instructeur_dossier_path(procedure_id, dossier_id), method: :post, role: 'menuitem') do
%span.icon.standby
%span.dropdown-description= t('instructeurs.dossiers.header.banner.button_delay_expiration')
- if archived
- menu.with_item do
= link_to( unarchive_instructeur_dossier_path(procedure_id, dossier_id), role: 'menuitem', method: :patch) do
%span.icon.unarchive
%span.dropdown-description
Désarchiver le dossier
- else
- menu.with_item do
= link_to( archive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, role: 'menuitem') do
%span.icon.archive
%span.dropdown-description
Archiver le dossier
- menu.with_item(class: 'danger') do
= link_to(instructeur_dossier_path(procedure_id, dossier_id), method: :delete, role: 'menuitem') do
%span.icon.delete
%span.dropdown-description
= t('views.instructeurs.dossiers.delete_dossier')
- elsif Dossier::EN_CONSTRUCTION_OU_INSTRUCTION.include?(state)
- if dossier_is_followed

View file

@ -1,5 +1,7 @@
%span.dropdown{ data: { controller: 'menu-button', popover: 'true' } }
%button.fr-btn.fr-btn--tertiary.fr-btn--sm.fr-mr-2w.dropdown-button{ data: { menu_button_target: 'button' } }
= render Dropdown::MenuComponent.new(wrapper: :div, button_options: { class: ['fr-btn--secondary', 'fr-btn--sm', 'fr-mr-1w'] }, menu_options: { id: 'filter-menu', class:['left-aligned'] }) do |menu|
- menu.with_button_inner_html do
= t('views.instructeurs.dossiers.filters.title')
#filter-menu.dropdown-content.left-aligned.fade-in-down{ data: { menu_button_target: 'menu' } }
- menu.with_form do
= render Dossiers::FilterComponent.new(procedure: procedure, procedure_presentation: @procedure_presentation, statut: statut)

View file

@ -62,10 +62,10 @@
= render Dossiers::NotifiedToggleComponent.new(procedure: @procedure, procedure_presentation: @procedure_presentation)
.fr-ml-auto
%span.dropdown{ data: { controller: 'menu-button', popover: 'true' } }
%button.fr-btn.fr-btn--sm.fr-btn--tertiary.dropdown-button.fr-ml-1w{ data: { menu_button_target: 'button' } }
= render Dropdown::MenuComponent.new(wrapper: :span, button_options: { class: ['fr-btn--sm', 'fr-btn--secondary'] }, menu_options: { id: 'custom-menu' }) do |menu|
- menu.with_button_inner_html do
= t('views.instructeurs.dossiers.personalize')
#custom-menu.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } }
- menu.with_form do
= form_tag update_displayed_fields_instructeur_procedure_path(@procedure), method: :patch, class: 'dropdown-form large columns-form' do
= hidden_field_tag :values, nil
= react_component("ComboMultiple",
@ -120,6 +120,7 @@
%th.action-col.follow-col
Actions
%tr
%tbody

View file

@ -1,6 +1,6 @@
- invites = dossier.invites.load
.dropdown.invite-user-action{ data: { controller: 'menu-button', popover: 'true' } }
%button.button.dropdown-button{ data: { menu_button_target: 'button' } }
= render Dropdown::MenuComponent.new(wrapper: :span, wrapper_options: {class: 'invite-user-action'}, button_options: { class: ['fr-btn--secondary'] }, menu_options: { id: 'invite-content' }) do |menu|
- menu.with_button_inner_html do
%span.icon.person
- if invites.present?
= t('views.invites.dropdown.view_invited_people')
@ -10,6 +10,5 @@
= t('views.invites.dropdown.invite_to_view')
- else
= t('views.invites.dropdown.invite_to_edit')
#invite-content.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } }
- menu.with_form do
= render partial: "invites/form", locals: { dossier: dossier, invites: invites }

View file

@ -1,11 +1,9 @@
#search-modal.fr-header__search.fr-modal
.fr-container.fr-container-lg--fluid
%button.fr-btn--close.fr-btn{ "aria-controls" => "search-modal", :title => t('close_modal', scope: [:layouts, :header]) }= t('close_modal', scope: [:layouts, :header])
#search-473.fr-search-bar{ :role => "search" }
= form_tag "#{search_endpoint}", method: :get, class: "flex width-100" do
#search-473.fr-search-bar
= form_tag "#{search_endpoint}", method: :get, :role => "search", class: "flex width-100" do
= label_tag "q", t('views.users.dossiers.search.search_file'), class: 'fr-label'
= text_field_tag "q", "#{@search_terms if @search_terms.present?}", placeholder: t('views.users.dossiers.search.search_file'), aria: { label: t('views.users.dossiers.search.search_file') }, class: "fr-input"
= text_field_tag "q", "#{@search_terms if @search_terms.present?}", placeholder: t('views.users.dossiers.search.placeholder'), class: "fr-input"
%button.fr-btn{ title: t('views.users.dossiers.search.search_file') }
= image_tag "icons/search-blue.svg", alt: t('views.users.dossiers.search.search_file'), 'aria-hidden':'true', width: 24, height: 24, loading: 'lazy'
= t('views.users.dossiers.search.search_file')

View file

@ -1,15 +1,10 @@
.no-procedure
= image_tag "landing/hero/dematerialiser.svg", class: "paperless-logo", alt: ""
.baseline.center
%p
%span.simple= t('.line1')
%br
= t('.line2')
%br
= t('.line3')
.no-procedure-presentation
%p.simple= t('.line1')
%p= t('.line2')
%p= t('.line3')
%hr
%p
%span.small-simple= t('.are_you_new', app_name: APPLICATION_NAME.gsub("-","&#8209;")).html_safe
%br
%br
= link_to t('views.users.sessions.new.find_procedure'), t("links.common.faq.comment_trouver_ma_demarche_url"), title: new_tab_suffix(t('views.users.sessions.new.find_procedure')), class: "fr-btn fr-btn--secondary", **external_link_attributes
%p.small-simple= t('.are_you_new', app_name: APPLICATION_NAME.gsub("-","&#8209;")).html_safe
= link_to t('views.users.sessions.new.find_procedure'), t("links.common.faq.comment_trouver_ma_demarche_url"), title: new_tab_suffix(t('views.users.sessions.new.find_procedure')), class: "fr-btn fr-btn--secondary", **external_link_attributes

View file

@ -12,11 +12,8 @@
%p
%strong
= t('.procedure_management')
%br
= service.nom
%br
= service.organisme
%br
= service.adresse
%td{ width: "50%", valign: "top" }
%p

View file

@ -42,7 +42,7 @@
= type_de_champ.possible_values_sentence
%br
- if type_de_champ.too_many_possible_values?
= link_to "Voir toutes les valeurs possibles", prefill_type_de_champ_path(prefill_description.path, type_de_champ)
= link_to t("views.prefill_descriptions.edit.possible_values.link.text"), prefill_type_de_champ_path(prefill_description.path, type_de_champ), title: new_tab_suffix(t("views.prefill_descriptions.edit.possible_values.link.title")), **external_link_attributes
%tr{ class: prefillable ? "" : "fr-text-mention--grey" }
%th
= t("views.prefill_descriptions.edit.examples.title")

View file

@ -67,21 +67,21 @@
- if instructeur_dossier && expert_dossier
%td.action-col.follow-col
.dropdown{ data: { controller: 'menu-button' } }
%button.button.dropdown-button{ data: { menu_button_target: 'button' } }
= render Dropdown::MenuComponent.new(wrapper: :div, button_options: {class: ['fr-btn--sm']}) do |menu|
- menu.with_button_inner_html do
Actions
.dropdown-content{ data: { menu_button_target: 'menu' } }
%ul.dropdown-items
%li
= link_to(instructeur_dossier_path(procedure_id, p.dossier_id)) do
%span.icon.in-progress>
.dropdown-description
Voir le dossier
%li
= link_to(expert_avis_path(procedure_id, @dossier_avis_ids_h[p.dossier_id])) do
%span.icon.in-progress>
.dropdown-description
Donner mon avis
- menu.with_item do
= link_to(instructeur_dossier_path(procedure_id, p.dossier_id), role: 'menuitem') do
%span.icon.in-progress>
.dropdown-description
Voir le dossier
- menu.with_item do
= link_to(expert_avis_path(procedure_id, @dossier_avis_ids_h[p.dossier_id]), role: 'menuitem') do
%span.icon.in-progress>
.dropdown-description
Donner mon avis
- elsif instructeur_dossier
- if hidden_by_administration

View file

@ -1,15 +1,16 @@
.dropdown.help-dropdown{ data: { controller: 'menu-button' } }
%button.fr-btn.dropdown-button{ data: { menu_button_target: 'button' } }
= render Dropdown::MenuComponent.new(wrapper: :span, wrapper_options: { class: ['help-dropdown']}, menu_options: { id: "help-menu" }) do |menu|
- menu.with_button_inner_html do
= t('help')
#help-menu.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } }
%ul.dropdown-items
- title = dossier.brouillon? ? "Besoin daide pour remplir votre dossier ?" : "Une question sur votre dossier ?"
- title = dossier.brouillon? ? "Besoin daide pour remplir votre dossier ?" : "Une question sur votre dossier ?"
- if dossier.messagerie_available?
= render partial: 'shared/help/dropdown_items/messagerie_item',
locals: { dossier: dossier, title: title }
- elsif dossier.procedure.service.present?
= render partial: 'shared/help/dropdown_items/service_item',
locals: { service: dossier.procedure.service, title: title }
- if dossier.messagerie_available?
- menu.with_item do
= render partial: 'shared/help/dropdown_items/messagerie_item', locals: { dossier: dossier, title: title }
= render partial: 'shared/help/dropdown_items/faq_item'
- elsif dossier.procedure.service.present?
- menu.with_item do
= render partial: 'shared/help/dropdown_items/service_item',
locals: { service: dossier.procedure.service, title: title }
- menu.with_item do
= render partial: 'shared/help/dropdown_items/faq_item'

View file

@ -1,7 +1,8 @@
.dropdown.help-dropdown{ data: { controller: 'menu-button' } }
%button.fr-btn.dropdown-button{ data: { menu_button_target: 'button' } }
= render Dropdown::MenuComponent.new(wrapper: :span, wrapper_options: { class: ['help-dropdown']}, menu_options: { id: "help-menu" }) do |menu|
- menu.with_button_inner_html do
= t('help')
#help-menu.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } }
%ul.dropdown-items
= render partial: 'shared/help/dropdown_items/faq_item'
= render partial: 'shared/help/dropdown_items/email_item'
- menu.with_item do
= render partial: 'shared/help/dropdown_items/faq_item'
- menu.with_item do
= render partial: 'shared/help/dropdown_items/email_item'

View file

@ -1,10 +1,9 @@
.dropdown.help-dropdown{ data: { controller: 'menu-button' } }
%button.fr-btn.dropdown-button{ data: { menu_button_target: 'button' } }
= render Dropdown::MenuComponent.new(wrapper: :span, wrapper_options: { class: ['help-dropdown']}, menu_options: { id: "help-menu" }) do |menu|
- menu.with_button_inner_html do
= t('help')
#help-menu.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } }
%ul.dropdown-items
- if procedure.service.present?
= render partial: 'shared/help/dropdown_items/service_item',
locals: { service: procedure.service, title: "Une question sur cette démarche ?" }
= render partial: 'shared/help/dropdown_items/faq_item'
- if procedure.service.present?
- menu.with_item do
= render partial: 'shared/help/dropdown_items/service_item', locals: { service: procedure.service, title: "Une question sur cette démarche ?" }
- menu.with_item do
= render partial: 'shared/help/dropdown_items/faq_item'

View file

@ -1,5 +1,5 @@
%li
= mail_to CONTACT_EMAIL do
%li{ role: 'none' }
= mail_to CONTACT_EMAIL, role: 'menuitem' do
%span.icon.mail
.dropdown-description
%span.help-dropdown-title

View file

@ -1,9 +1,7 @@
%li
= link_to t("links.common.faq.url"), title: new_tab_suffix(t('help_dropdown.general_title')), **external_link_attributes do
%span.icon.help
.dropdown-description
%span.help-dropdown-title
= t('help_dropdown.problem_title')
%p
= t('help_dropdown.problem_description')
= link_to t("links.common.faq.url"), title: new_tab_suffix(t('help_dropdown.general_title')), **external_link_attributes, role: 'menuitem' do
%span.icon.help
.dropdown-description
%span.help-dropdown-title
= t('help_dropdown.problem_title')
%p
= t('help_dropdown.problem_description')

View file

@ -1,6 +1,5 @@
%li
= link_to messagerie_dossier_path(dossier) do
%span.icon.mail
.dropdown-description
%span.help-dropdown-title= title
%p Envoyez directement un message à linstructeur.
= link_to messagerie_dossier_path(dossier), role: 'menuitem' do
%span.icon.mail
.dropdown-description
%span.help-dropdown-title= title
%p Envoyez directement un message à linstructeur.

View file

@ -1,15 +1,14 @@
%li.help-dropdown-service
%span.icon.person
.dropdown-description
%span.help-dropdown-title= title
.help-dropdown-service-action
%p Contactez directement ladministration :
%p.help-dropdown-service-item
%span.icon.small.mail
= link_to service.email, "mailto:#{service.email}"
%p.help-dropdown-service-item
%span.icon.small.phone
= link_to service.telephone, service.telephone_url
%p.help-dropdown-service-item
%span.icon.small.clock
= service.horaires
%span.icon.person
.dropdown-description
%span.help-dropdown-title= title
.help-dropdown-service-action
%p Contactez directement ladministration :
%p.help-dropdown-service-item
%span.icon.small.mail
= link_to service.email, "mailto:#{service.email}", role: 'menuitem'
%p.help-dropdown-service-item
%span.icon.small.phone
= link_to service.telephone, service.telephone_url, role: 'menuitem'
%p.help-dropdown-service-item
%span.icon.small.clock
= service.horaires

View file

@ -10,8 +10,9 @@
= form_tag contact_path, method: :post, multipart: true, class: 'fr-form-group', data: {controller: :support } do
.description
%h2= t('.intro_html')
%br
.recommandations
%h2
= t('.intro_html')
%p.mandatory-explanation= t('asterisk_html', scope: [:utils])
- if !user_signed_in?
@ -34,7 +35,7 @@
- if link.present?
.support.card.featured.mb-4.ml-4.hidden{ id: "card-#{question_type}", "aria-hidden": true , data: { "support-target": "content" } }
.card-title
%p.card-title
= t('.our_answer')
.card-content
-# i18n-tasks-use t("support.index.#{question_type}.answer_html")

View file

@ -15,9 +15,10 @@
%li
- horaires = "#{I18n.t('users.procedure_footer.contact.schedule.prefix')}#{formatted_horaires(service.horaires)}"
= link_to service.telephone_url, class: 'fr-footer__top-link' do
= I18n.t('users.procedure_footer.contact.phone.link', service_telephone: service.telephone)
%br
= horaires
%p
= I18n.t('users.procedure_footer.contact.phone.link', service_telephone: service.telephone)
%p
= horaires
%li
= link_to I18n.t('users.procedure_footer.contact.stats.link'), statistiques_path(procedure.path), class: 'fr-footer__top-link', rel: 'noopener'

View file

@ -4,49 +4,51 @@
- has_transfer_action = dossier.user == current_user
- has_actions = has_edit_action || has_delete_action || has_new_dossier_action || has_transfer_action
- if has_actions
.dropdown.user-dossier-actions{ data: { controller: 'menu-button' } }
%button.fr-btn.fr-btn--secondary.dropdown-button{ data: { menu_button_target: 'button' } }
= render Dropdown::MenuComponent.new(wrapper: :div, wrapper_options: {class: 'invite-user-actions'}, menu_options: {id: dom_id(dossier, :actions_menu)}, button_options: {class: 'fr-btn--sm fr-btn--secondary'}) do |menu|
- menu.with_button_inner_html do
= t('views.users.dossiers.dossier_action.actions')
.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' }, id: dom_id(dossier, :actions_menu) }
%ul.dropdown-items
- if has_edit_action
- if dossier.brouillon?
%li
= link_to(url_for_dossier(dossier)) do
%span.icon.edit
.dropdown-description
= t('views.users.dossiers.dossier_action.edit_draft')
- else
%li
= link_to modifier_dossier_path(dossier) do
%span.icon.edit
.dropdown-description
= t('views.users.dossiers.dossier_action.edit_dossier')
- if has_transfer_action
%li
= link_to transferer_dossier_path(dossier) do
%span.icon.person
.dropdown-description
= t('views.users.dossiers.dossier_action.transfer_dossier')
- if has_edit_action
- if dossier.brouillon?
- menu.with_item do
= link_to(url_for_dossier(dossier), role: 'menuitem') do
%span.icon.edit
.dropdown-description
= t('views.users.dossiers.dossier_action.edit_draft')
- else
- menu.with_item do
= link_to(modifier_dossier_path(dossier), role: 'menuitem') do
%span.icon.edit
.dropdown-description
= t('views.users.dossiers.dossier_action.edit_dossier')
- if has_new_dossier_action
%li
= link_to procedure_lien(dossier.procedure) do
%span.icon.new-folder
.dropdown-description
= t('views.users.dossiers.dossier_action.start_other_dossier')
%li
= link_to clone_dossier_path(dossier), method: :post do
%span.icon.new-folder
.dropdown-description
= t('views.users.dossiers.dossier_action.clone')
- if has_transfer_action
- menu.with_item do
= link_to(transferer_dossier_path(dossier), role: 'menuitem') do
%span.icon.person
.dropdown-description
= t('views.users.dossiers.dossier_action.transfer_dossier')
- if has_delete_action
%li.danger
= link_to delete_dossier_dossier_path(dossier), method: :patch, data: { disable: true, confirm: "En continuant, vous allez supprimer ce dossier ainsi que les informations quil contient. Toute suppression entraîne lannulation de la démarche en cours.\n\nConfirmer la suppression ?" } do
%span.icon.delete
.dropdown-description
= t('views.users.dossiers.dossier_action.delete_dossier')
- if has_new_dossier_action
- menu.with_item do
= link_to(procedure_lien(dossier.procedure), role: 'menuitem') do
%span.icon.new-folder
.dropdown-description
= t('views.users.dossiers.dossier_action.start_other_dossier')
- menu.with_item do
= link_to(clone_dossier_path(dossier), method: :post, role: 'menuitem') do
%span.icon.new-folder
.dropdown-description
= t('views.users.dossiers.dossier_action.clone')
- if has_delete_action
- menu.with_item(class: 'danger') do
= link_to(delete_dossier_dossier_path(dossier), role: 'menuitem', method: :patch, data: { disable: true, confirm: "En continuant, vous allez supprimer ce dossier ainsi que les informations quil contient. Toute suppression entraîne lannulation de la démarche en cours.\n\nConfirmer la suppression ?" }) do
%span.icon.delete
.dropdown-description
= t('views.users.dossiers.dossier_action.delete_dossier')

View file

@ -1,8 +1,8 @@
.dropdown.edit-identity-action{ data: { controller: 'menu-button', popover: 'true' } }
%button.button.dropdown-button{ data: { menu_button_target: 'button' } }
= render Dropdown::MenuComponent.new(wrapper: :div, wrapper_options: {class: ['edit-identity-action']}, menu_options: { class:['edit-identity-content'] }) do |menu|
- menu.with_button_inner_html do
= t("views.shared.dossiers.demande.my_identity")
#edit-identity-content.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' } }
- menu.with_form do
- if dossier.procedure.for_individual
= render partial: "shared/dossiers/identite_individual", locals: { individual: dossier.individual }

View file

@ -10,5 +10,5 @@
.container
- if !@dossier.read_only?
= link_to t('views.users.dossiers.demande.edit_dossier'), modifier_dossier_path(@dossier), class: 'button accepted edit-form', title: "Modifier mon dossier tant qu'il n'est pas passé en instruction"
= link_to t('views.users.dossiers.demande.edit_dossier'), modifier_dossier_path(@dossier), class: 'fr-btn fr-btn-sm', 'title'=> "Modifier mon dossier tant qu'il n'est pas passé en instruction"
.clearfix

View file

@ -18,7 +18,7 @@
.header-actions
= render partial: 'invites/dropdown', locals: { dossier: dossier }
- if dossier.can_be_updated_by_user? && !current_page?(modifier_dossier_path(dossier))
= link_to t('views.users.dossiers.show.header.edit_dossier'), modifier_dossier_path(dossier), class: 'button accepted edit-form',
= link_to t('views.users.dossiers.show.header.edit_dossier'), modifier_dossier_path(dossier), class: 'fr-btn fr-btn-sm',
title: { label: t('views.users.dossiers.show.header.edit_dossier_title') }
= render(partial: 'users/dossiers/show/print_dossier', locals: { dossier: dossier })

View file

@ -1,6 +1 @@
.dropdown.print-menu-opener{ data: { controller: 'menu-button' } }
%button.button.dropdown-button.icon-only{ title: t('views.users.dossiers.show.header.print'), 'aria-label': 'imprimer', data: { menu_button_target: 'button' } }
%span.icon.printer
%ul#print-menu.print-menu.dropdown-content{ data: { menu_button_target: 'menu' } }
%li
= link_to t('views.users.dossiers.show.header.print_dossier'), dossier_path(dossier, format: :pdf), target: "_blank", rel: "noopener", class: "menu-item menu-link"
= link_to t('views.users.dossiers.show.header.print'), dossier_path(dossier, format: :pdf), target: "_blank", rel: "noopener", title: t('views.users.dossiers.show.header.print_dossier'), class: 'fr-btn fr-icon-printer-line fr-btn--tertiary'

View file

@ -4,7 +4,7 @@
= turbo_stream.hide_all(@to_hide)
- @to_update.each do |champ|
= fields_for champ.input_name, champ do |form|
= turbo_stream.morph champ.input_group_id do
= turbo_stream.replace champ.input_group_id do
= render EditableChamp::EditableChampComponent.new champ:, form:
= turbo_stream.remove_all(".editable-champ .spinner-removable");

View file

@ -112,6 +112,9 @@ en:
champ_remove: Remove
champ_unavailable: Unavailable
possible_values:
link:
title: All possible values
text: See all possible values
title: Values
text_html: A short text
textarea_html: A long text
@ -314,7 +317,8 @@ en:
demande:
edit_dossier: "Edit file"
search:
search_file: Search a file
placeholder: Search a file
search_file: Search
index:
dossiers: "Files"
dossiers_list:

View file

@ -103,6 +103,9 @@ fr:
champ_remove: Retirer
champ_unavailable: Indisponible
possible_values:
link:
title: Toutes les valeurs possibles
text: Voir toutes les valeurs possibles
title: Valeurs
text_html: Un texte court
textarea_html: Un texte long
@ -310,7 +313,8 @@ fr:
demande:
edit_dossier: "Modifier le dossier"
search:
search_file: Rechercher un dossier
placeholder: Rechercher un dossier
search_file: Rechercher
index:
dossiers: "Dossiers"
dossiers_list:

View file

@ -2,7 +2,8 @@ en:
support:
index:
contact: Contact
intro_html: Contact us via this form and we will answer you as quickly as possible.<br>Make sure you provide all the required information so we can help you in the best way.
intro_html: "<p>Contact us via this form and we will answer you as quickly as possible.</p>
<p>Make sure you provide all the required information so we can help you in the best way.</p>"
your_question: Your question
our_answer: 👉 Our answer
notice_pj_product: A screenshot can help us identify the element to improve.
@ -10,35 +11,28 @@ en:
procedure_info:
question: I've encountered a problem while completing my application
answer_html: "<p>Are you sure that all the mandatory fields (<span class= mandatory> * </span>) are properly filled?
<p>If you have questions about the information requested, contact the service in charge of the procedure.</p>
<p><a href=%{link_procedure_info}>Find more information</a></p>"
<p>If you have questions about the information requested, <a href=\"%{link_procedure_info}\">contact the service in charge of the procedure (FAQ)</a>.</p>"
instruction_info:
question: I have a question about the instruction of my application
answer_html: "<p>If you have questions about the instruction of your application (response delay for example), contact directly the instructor via our mail system.</p>
<p><a href=%{link_instruction_info}>Find more information</a></p>
<br>
<p>If you are facing technical issues on the website, use the form below. We will not be able to inform you about the instruction of your application.</p>"
answer_html: "<p>If you have questions about the instruction of your application (response delay for example), <a href=\"%{link_instruction_info}\">contact directly the instructor via our mail system (FAQ)</a>.</p>
<p>If you are facing technical issues on the website, use the form below. We will not be able to inform you about the instruction of your application.</p>"
product:
question: I have an idea to improve the website
answer_html: "<p>Got an idea? Please check our <strong>enhancement dashboard</strong></p>
answer_html: "<p>Got an idea? <a href=\"%{link_product}\" rel=\"noopener noreferrer\" target=\"_blank\" title=\"Open features dashboard - New tab\">Please check our enhancement dashboard</a> :</p>
<ul><li>Vote for your priority improvements</li>
<li>Share your own ideas</li></ul>
<p><strong><a href=%{link_product}>➡ Access the enhancement dashboard</a></strong></p>"
<li>Share your own ideas</li></ul>"
lost_user:
question: I am having trouble finding the procedure I am looking for
answer_html: "<p>We invite you to contact the administration in charge of the procedure so they can provide you the link.
It should look like this: %{base_url}/commencer/NOM_DE_LA_DEMARCHE.</p>
<br>
<p>You can find here the most popular procedures (licence, detr, etc.) :</p>
<p><a href=%{link_lost_user}>%{link_lost_user}</a></p>"
It should look like this: <code>%{base_url}/commencer/NOM_DE_LA_DEMARCHE</code>.</p>
<p>You can <a href=\"%{link_lost_user}\">find here the most popular procedures (licence, detr, etc.)</a>.</p>"
other:
question: Other topic
admin:
your_question: Your question
admin_intro_html: "<p>As an administration, you can contact us through this form. We'll answer you as quickly as possibly by e-mail or phone.</p>
<br>
<p><strong>Caution, this form is dedicated to public bodies only.</strong>
It does not concern individuals, companies nor associations (except those recognised of public utility). If you belong to one of these categories, contact us <a href=%{contact_path}>here</a>.</p>"
<p><strong>Caution, this form is dedicated to public bodies only.</strong>
It does not concern individuals, companies nor associations (except those recognised of public utility). If you belong to one of these categories, contact us <a href=\"%{contact_path}\">here</a>.</p>"
contact_team: Contact our team
pro_phone_number: Professional phone number (direct line)
pro_mail: Professional email address

View file

@ -2,7 +2,8 @@ fr:
support:
index:
contact: Contact
intro_html: Contactez-nous via ce formulaire et nous vous répondrons dans les plus brefs délais.<br>Pensez bien à nous donner le plus dinformations possible pour que nous puissions vous aider au mieux.
intro_html: "<p>Contactez-nous via ce formulaire et nous vous répondrons dans les plus brefs délais.</p>
<p>Pensez bien à nous donner le plus dinformations possible pour que nous puissions vous aider au mieux.</p>"
your_question: Votre question
our_answer: 👉 Notre réponse
notice_pj_product: Une capture décran peut nous aider à identifier plus facilement lendroit à améliorer.
@ -10,35 +11,28 @@ fr:
procedure_info:
question: Jai un problème lors du remplissage de mon dossier
answer_html: "<p>Avez-vous bien vérifié que tous les champs obligatoires (<span class= mandatory> * </span>) sont remplis ?
<p>Si vous avez des questions sur les informations à saisir, contactez les services en charge de la démarche.</p>
<p><a href=%{link_procedure_info}>En savoir plus</a></p>"
<p>Si vous avez des questions sur les informations à saisir, <a href=\"%{link_procedure_info}\">contactez les services en charge de la démarche (FAQ)</a>.</p>"
instruction_info:
question: Jai une question sur linstruction de mon dossier
answer_html: "<p>Si vous avez des questions sur linstruction de votre dossier (par exemple sur les délais), nous vous invitons à contacter directement les services qui instruisent votre dossier par votre messagerie.</p>
<p><a href=%{link_instruction_info}>En savoir plus</a></p>
<br>
<p>Si vous souhaitez poser une question pour un problème technique sur le site, utilisez le formulaire ci-dessous. Nous ne pourrons pas vous renseigner sur linstruction de votre dossier.</p>"
answer_html: "<p>Si vous avez des questions sur linstruction de votre dossier (par exemple sur les délais), nous vous invitons à <a href=\"%{link_instruction_info}\">contacter directement les services qui instruisent votre dossier par votre messagerie (FAQ)</a>.</p>
<p>Si vous souhaitez poser une question pour un problème technique sur le site, utilisez le formulaire ci-dessous. Nous ne pourrons pas vous renseigner sur linstruction de votre dossier.</p>"
product:
question: Jai une idée damélioration pour votre site
answer_html: "<p>Une idée ? Pensez à consulter notre <strong>tableau de bord des améliorations</strong></p>
<ul><li>Votez pour vos améliorations prioritaires;</li>
<li>Proposez votre propre idée.</li></ul>
<p><strong><a href=%{link_product}>➡ Accéder au tableau des améliorations</a></strong></p>"
answer_html: "<p>Une idée ? Pensez à <a href=\"%{link_product}\" rel=\"noopener noreferrer\" target=\"_blank\" title=\"Ouvrir le tableau des évolutions - Nouvel onglet\">consulter notre tableau de bord des améliorations</a> :</p>
<ul><li>Votez pour vos améliorations prioritaires,</li>
<li>Proposez votre propre idée.</li></ul>"
lost_user:
question: Je ne trouve pas la démarche que je veux faire
answer_html: "<p>Nous vous invitons à contacter ladministration en charge de votre démarche pour quelle vous indique le lien à suivre. Celui-ci devrait ressembler à cela : %{base_url}/commencer/NOM_DE_LA_DEMARCHE.</p>
<br>
<p>Vous pouvez aussi consulter ici la liste de nos démarches les plus frequentes (permis, detr etc) :</p>
<p><a href=%{link_lost_user}>%{link_lost_user}</a></p>"
answer_html: "<p>Nous vous invitons à contacter ladministration en charge de votre démarche pour quelle vous indique le lien à suivre. Celui-ci devrait ressembler à cela : <code>%{base_url}/commencer/NOM_DE_LA_DEMARCHE</code>.</p>
<p>Vous pouvez aussi <a href=\"%{link_lost_user}\">consulter ici la liste de nos démarches les plus fréquentes (permis, detr etc)</a>.</p>"
other:
question: Autre sujet
admin:
your_question: Votre question
admin_intro_html: "<p>En tant quadministration, vous pouvez nous contactez via ce formulaire. Nous vous répondrons dans les plus brefs délais, par email ou par téléphone.</p>
<br>
<p><strong>Attention, ce formulaire est réservé uniquement aux organismes publics.</strong>
Il ne concerne ni les particuliers, ni les entreprises, ni les associations (sauf celles reconnues dutilité publique). Si c'est votre cas, rendez-vous sur notre
<a href=%{contact_path}>formulaire de contact public</a>.</p>"
<p><strong>Attention, ce formulaire est réservé uniquement aux organismes publics.</strong>
Il ne concerne ni les particuliers, ni les entreprises, ni les associations (sauf celles reconnues dutilité publique). Si c'est votre cas, rendez-vous sur notre
<a href=\"%{contact_path}\">formulaire de contact public</a>.</p>"
contact_team: Contactez notre équipe
pro_phone_number: Numéro de téléphone professionnel (ligne directe)
pro_mail: Adresse e-mail professionnelle

View file

@ -0,0 +1,5 @@
class AddRemindedAtToAvis < ActiveRecord::Migration[6.1]
def change
add_column :avis, :reminded_at, :datetime
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2023_01_17_094317) do
ActiveRecord::Schema.define(version: 2023_01_26_145329) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
@ -164,6 +164,7 @@ ActiveRecord::Schema.define(version: 2023_01_17_094317) do
t.text "introduction"
t.datetime "revoked_at"
t.datetime "updated_at", null: false
t.datetime "reminded_at"
t.index ["claimant_id"], name: "index_avis_on_claimant_id"
t.index ["dossier_id"], name: "index_avis_on_dossier_id"
t.index ["experts_procedure_id"], name: "index_avis_on_experts_procedure_id"

View file

@ -32,25 +32,10 @@ namespace :benchmarks do
desc 'Attestation Template parser'
task attestation_template_parser: :environment do
progress = ProgressReport.new(AttestationTemplate.count)
AttestationTemplate.find_each do |template|
parsed = TagsSubstitutionConcern::TagsParser.parse(template.body)
serialized = parsed.map do |token|
case token
in { tag: tag }
"--#{tag}--"
in { text: text }
text
end
end.join('')
if serialized != template.body
throw "Template '#{serialized}' is not eq '#{template.body}' with attestation template #{template.id}"
end
progress.inc
rescue => e
pp "Error with attestation template #{template.id}"
throw e
procedure = Procedure.find(68139)
Benchmark.bm do |x|
x.report("Empty") { TagsSubstitutionConcern::TagsParser.parse('') }
x.report("Démarche 68139") { TagsSubstitutionConcern::TagsParser.parse(procedure.attestation_template.body) }
end
progress.finish
end
end

View file

@ -0,0 +1,20 @@
namespace :after_party do
desc 'Deployment task: fix_dossier_transfer_with_uppercase'
task fix_dossier_transfer_with_uppercase: :environment do
puts "Running deploy task 'fix_dossier_transfer_with_uppercase'"
# in production, about 1000, no need to track progress
DossierTransfer.all.find_each do |dt|
if /A-Z/.match?(dt.email)
dt.email = dt.email.downcase
dt.save
end
end
# Put your task implementation HERE.
# Update task as completed. If you remove the line below, the task will
# run with every deploy (or every time you call after_party:run).
AfterParty::TaskRecord
.create version: AfterParty::TaskRecorder.new(__FILE__).timestamp
end
end

View file

@ -865,6 +865,10 @@ describe Instructeurs::DossiersController, type: :controller do
}
end
before do
allow(PiecesJustificativesService).to receive(:generate_dossier_export).with([dossier], include_infos_administration: true).and_call_original
end
it 'includes an attachment' do
expect(subject.headers['Content-Disposition']).to start_with('attachment; ')
end

View file

@ -26,6 +26,11 @@ describe Users::TransfersController, type: :controller do
it { expect(DossierTransfer.last.dossiers).to eq([dossier]) }
end
context 'with upper case email' do
let(:email) { "Test@rspec.net" }
it { expect(DossierTransfer.last.email).to eq(email.strip.downcase) }
end
shared_examples 'email error' do
it { expect { subject }.not_to change { DossierTransfer.count } }
it { expect(flash.alert).to match([/invalide/]) }

View file

@ -1,7 +1,7 @@
describe ActiveStorage::DownloadableFile do
let(:dossier) { create(:dossier, :en_construction) }
subject(:list) { ActiveStorage::DownloadableFile.create_list_from_dossiers(Dossier.where(id: dossier.id)) }
subject(:list) { ActiveStorage::DownloadableFile.create_list_from_dossiers(Dossier.where(id: dossier.id), with_bills: true, with_champs_private: true) }
describe 'create_list_from_dossiers' do
context 'when no piece_justificative is present' do

View file

@ -1,5 +1,5 @@
describe Champs::DateChamp do
let(:date_champ) { build(:champ_date) }
let(:date_champ) { create(:champ_date) }
describe '#convert_to_iso8601' do
it 'preserves nil' do
@ -37,6 +37,12 @@ describe Champs::DateChamp do
champ.save
expect(champ.reload.value).to eq("2023-12-21")
end
it 'converts to nil if false iso' do
champ = champ_with_value("2023-27-02")
champ.save
expect(champ.reload.value).to eq(nil)
end
end
def champ_with_value(number)

View file

@ -516,7 +516,7 @@ describe TagsSubstitutionConcern, type: :model do
describe 'parser' do
it do
tokens = TagsSubstitutionConcern::TagsParser.parse("hello world --public--, --numéro du dossier--, un test--yolo-- encore du text\n---\n encore du text")
tokens = TagsSubstitutionConcern::TagsParser.parse("hello world --public--, --numéro du dossier--, un test--yolo-- encore du text\n---\n encore du text --- et encore du text\n--tag--")
expect(tokens).to eq([
{ text: "hello world " },
{ tag: "public" },
@ -524,14 +524,15 @@ describe TagsSubstitutionConcern, type: :model do
{ tag: "numéro du dossier" },
{ text: ", un test" },
{ tag: "yolo" },
{ text: " encore du text\n" + "---\n" + " encore du text" }
{ text: " encore du text\n" + "---\n" + " encore du text --- et encore du text\n" },
{ tag: "tag" }
])
end
it 'allow for - before tag' do
tokens = TagsSubstitutionConcern::TagsParser.parse("hello --yolo-- -- before-- --after -- -- around -- world ---numéro-du - dossier--")
tokens = TagsSubstitutionConcern::TagsParser.parse("-----------------\nhello --yolo-- -- before-- --after -- -- around -- world ---numéro-du - dossier--")
expect(tokens).to eq([
{ text: "hello " },
{ text: "-----------------\nhello " },
{ tag: "yolo" },
{ text: " " },
{ tag: "before" },

View file

@ -239,7 +239,7 @@ describe Dossier do
end
describe "#rebase" do
let(:procedure) { create(:procedure, :with_type_de_champ_mandatory, :with_yes_no, :with_repetition, :with_datetime) }
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :text, mandatory: true }, { type: :repetition, children: [{ type: :text }] }, { type: :datetime }, { type: :yes_no }, { type: :integer_number }]) }
let(:dossier) { create(:dossier, procedure: procedure) }
let(:yes_no_type_de_champ) { procedure.active_revision.types_de_champ_public.find { |tdc| tdc.type_champ == TypeDeChamp.type_champs.fetch(:yes_no) } }
@ -248,6 +248,8 @@ describe Dossier do
let(:text_champ) { dossier.champs_public.find(&:mandatory?) }
let(:rebased_text_champ) { dossier.champs_public.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:text) } }
let(:rebased_number_champ) { dossier.champs_public.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:integer_number) } }
let(:datetime_type_de_champ) { procedure.active_revision.types_de_champ_public.find { |tdc| tdc.type_champ == TypeDeChamp.type_champs.fetch(:datetime) } }
let(:datetime_champ) { dossier.champs_public.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:datetime) } }
let(:rebased_datetime_champ) { dossier.champs_public.find { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:date) } }
@ -287,7 +289,7 @@ describe Dossier do
libelle = text_type_de_champ.libelle
expect(dossier.revision).to eq(procedure.published_revision)
expect(dossier.champs_public.size).to eq(4)
expect(dossier.champs_public.size).to eq(5)
expect(repetition_champ.rows.size).to eq(2)
expect(repetition_champ.rows[0].size).to eq(1)
expect(repetition_champ.rows[1].size).to eq(1)
@ -299,7 +301,7 @@ describe Dossier do
expect(procedure.revisions.size).to eq(3)
expect(dossier.revision).to eq(procedure.published_revision)
expect(dossier.champs_public.size).to eq(4)
expect(dossier.champs_public.size).to eq(5)
expect(rebased_text_champ.value).to eq(text_champ.value)
expect(rebased_text_champ.type_de_champ_id).not_to eq(text_champ.type_de_champ_id)
expect(rebased_datetime_champ.type_champ).to eq(TypeDeChamp.type_champs.fetch(:date))
@ -309,6 +311,7 @@ describe Dossier do
expect(rebased_repetition_champ.rows[1].size).to eq(2)
expect(rebased_text_champ.rebased_at).not_to be_nil
expect(rebased_datetime_champ.rebased_at).not_to be_nil
expect(rebased_number_champ.rebased_at).to be_nil
end
end

View file

@ -1405,6 +1405,46 @@ describe Dossier do
end
end
describe '#geo_data' do
let(:dossier) { create(:dossier) }
let(:type_de_champ_carte) { create(:type_de_champ_carte, procedure: dossier.procedure) }
let(:geo_area) { create(:geo_area) }
let(:champ_carte) { create(:champ_carte, type_de_champ: type_de_champ_carte, geo_areas: [geo_area]) }
context "without data" do
it { expect(dossier.geo_data?).to be_falsey }
end
context "with geo data in public champ" do
before do
dossier.champs_public << champ_carte
end
it { expect(dossier.geo_data?).to be_truthy }
end
context "with geo data in private champ" do
before do
dossier.champs_private << champ_carte
end
it { expect(dossier.geo_data?).to be_truthy }
end
it "should solve N+1 problem" do
dossier.champs_public << create_list(:champ_carte, 3, type_de_champ: type_de_champ_carte, geo_areas: [create(:geo_area)])
count = 0
callback = lambda { |*_args| count += 1 }
ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do
dossier.geo_data?
end
expect(count).to eq(1)
end
end
describe 'dossier_operation_log after dossier deletion' do
let(:dossier) { create(:dossier) }
let(:dossier_operation_log) { create(:dossier_operation_log, dossier: dossier) }
@ -1853,6 +1893,19 @@ describe Dossier do
before { dossier.champs_public << champ_piece_justificative }
it { expect(Champs::PieceJustificativeChamp.where(dossier: new_dossier).first.piece_justificative_file.first.blob).to eq(champ_piece_justificative.piece_justificative_file.first.blob) }
end
context 'for Champs::AddressChamp, original_champ.data is duped' do
let(:dossier) { create(:dossier) }
let(:type_de_champs_adress) { create(:type_de_champ_address, procedure: dossier.procedure) }
let(:etablissement) { create(:etablissement) }
let(:champ_address) { create(:champ_address, type_de_champ: type_de_champs_adress, external_id: 'Address', data: { city_code: '75019' }) }
before { dossier.champs_public << champ_address }
it { expect(Champs::AddressChamp.where(dossier: dossier).first.data).not_to be_nil }
it { expect(Champs::AddressChamp.where(dossier: dossier).first.external_id).not_to be_nil }
it { expect(Champs::AddressChamp.where(dossier: new_dossier).first.external_id).to eq(champ_address.external_id) }
it { expect(Champs::AddressChamp.where(dossier: new_dossier).first.data).to eq(champ_address.data) }
end
end
context 'private are renewd' do

View file

@ -854,4 +854,15 @@ describe ProcedurePresentation do
it { is_expected.to eq(sorted_ids) }
end
end
describe '#field_enum' do
context "field is groupe_instructeur" do
let!(:gi_2) { instructeur.groupe_instructeurs.create(label: 'gi2', procedure:) }
let!(:gi_3) { instructeur.groupe_instructeurs.create(label: 'gi3', procedure: create(:procedure)) }
subject { procedure_presentation.field_enum('groupe_instructeur/id') }
it { is_expected.to eq([['défaut', procedure.defaut_groupe_instructeur.id], ['gi2', gi_2.id]]) }
end
end
end

View file

@ -872,4 +872,21 @@ describe ProcedureRevision do
it { expect(draft.dependent_conditions(first_champ)).to eq([second_champ]) }
it { expect(draft.dependent_conditions(second_champ)).to eq([]) }
end
describe 'only_present_on_draft?' do
let(:procedure) { create(:procedure, types_de_champ_public: [{ libelle: 'Un champ texte' }]) }
let(:type_de_champ) { procedure.draft_revision.types_de_champ_public.first }
it {
expect(type_de_champ.only_present_on_draft?).to be_truthy
procedure.publish!
expect(type_de_champ.only_present_on_draft?).to be_falsey
procedure.draft_revision.remove_type_de_champ(type_de_champ.stable_id)
expect(type_de_champ.only_present_on_draft?).to be_falsey
expect(type_de_champ.revisions.count).to eq(1)
procedure.publish_revision!
expect(type_de_champ.only_present_on_draft?).to be_falsey
expect(type_de_champ.revisions.count).to eq(1)
}
end
end

View file

@ -1,10 +1,11 @@
describe PiecesJustificativesService do
describe '.liste_documents' do
let(:for_expert) { false }
let(:with_champs_private) { true }
let(:with_bills) { true }
subject do
PiecesJustificativesService
.liste_documents(Dossier.where(id: dossier.id), for_expert)
.liste_documents(Dossier.where(id: dossier.id), with_bills:, with_champs_private:)
.map(&:first)
end
@ -59,8 +60,8 @@ describe PiecesJustificativesService do
it { expect(subject).to match_array(private_pj_champ.call(dossier).piece_justificative_file.attachments) }
context 'for expert' do
let(:for_expert) { true }
context 'without private champ' do
let(:with_champs_private) { false }
it { expect(subject).to be_empty }
end
@ -171,8 +172,8 @@ describe PiecesJustificativesService do
expect(subject).to match_array([dossier_bs.serialized.attachment, dossier_bs.signature.attachment])
end
context 'for expert' do
let(:for_expert) { true }
context 'without bills' do
let(:with_bills) { false }
it { expect(subject).to be_empty }
end
@ -192,8 +193,8 @@ describe PiecesJustificativesService do
it { expect(subject).to match_array(dol.serialized.attachment) }
context 'for expert' do
let(:for_expert) { true }
context 'without bills' do
let(:with_bills) { false }
it { expect(subject).to be_empty }
end

View file

@ -18,7 +18,7 @@ describe 'As an administrateur I wanna clone a procedure', js: true do
scenario do
visit admin_procedures_path
expect(page.find_by_id('procedures')['data-item-count']).to eq('1')
page.all('.procedures-actions-btn').first.click
page.all('.admin-procedures-list-row .dropdown .fr-btn').first.click
page.all('.clone-btn').first.click
visit admin_procedures_path(statut: "brouillons")
expect(page.find_by_id('procedures')['data-item-count']).to eq('1')

View file

@ -116,7 +116,6 @@ describe 'Inviting an expert:' do
click_on '1 avis à donner'
click_on avis.dossier.user.email
find(:css, '[aria-controls=print-pj-menu]').click
click_on 'Télécharger le dossier et toutes ses pièces jointes'
# For some reason, clicking the download link does not trigger the download in the headless browser ;
# So we need to go to the download link directly

View file

@ -26,6 +26,21 @@ describe 'dropdown list with other option activated', js: true do
find('.radios').find('label:last-child').find('input').select_option
expect(page).to have_selector('.drop_down_other', visible: true)
end
scenario "Getting back from other save the new option", js: true do
fill_individual
choose "Autre"
fill_in("Veuillez saisir votre autre choix", with: "My choice")
wait_until { user_dossier.champs_public.first.value == "My choice" }
expect(user_dossier.champs_public.first.value).to eq("My choice")
choose "Secondary 1.1"
wait_until { user_dossier.champs_public.first.value == "Secondary 1.1" }
expect(user_dossier.champs_public.first.value).to eq("Secondary 1.1")
end
end
context 'with select' do

View file

@ -27,7 +27,7 @@ describe 'users/dossiers/show/header.html.haml', type: :view do
end
it 'can download the dossier' do
expect(rendered).to have_text("Tout le dossier")
expect(rendered).to have_selector('a[title="Tout le dossier"]')
end
end
@ -45,7 +45,7 @@ describe 'users/dossiers/show/header.html.haml', type: :view do
end
it 'can download the dossier' do
expect(rendered).to have_text("Tout le dossier")
expect(rendered).to have_selector('a[title="Tout le dossier"]')
end
it 'does not display a new procedure link' do
@ -68,7 +68,7 @@ describe 'users/dossiers/show/header.html.haml', type: :view do
end
it 'can download the dossier' do
expect(rendered).to have_text("Tout le dossier")
expect(rendered).to have_selector('a[title="Tout le dossier"]')
end
it 'displays a new procedure link' do
@ -105,7 +105,7 @@ describe 'users/dossiers/show/header.html.haml', type: :view do
end
it 'can download the dossier' do
expect(rendered).to have_text("Tout le dossier")
expect(rendered).to have_selector('a[title="Tout le dossier"]')
end
end
@ -124,7 +124,7 @@ describe 'users/dossiers/show/header.html.haml', type: :view do
end
it 'can not download the dossier' do
expect(rendered).not_to have_text("Tout le dossier")
expect(rendered).not_to have_selector('a[title="Tout le dossier"]')
end
end
end