commit
3f0cb4d12d
36 changed files with 369 additions and 209 deletions
1
Gemfile
1
Gemfile
|
@ -71,7 +71,6 @@ gem 'rack-attack'
|
||||||
gem 'rails'
|
gem 'rails'
|
||||||
gem 'rails-i18n' # Locales par défaut
|
gem 'rails-i18n' # Locales par défaut
|
||||||
gem 'rake-progressbar', require: false
|
gem 'rake-progressbar', require: false
|
||||||
gem 'react-rails'
|
|
||||||
gem 'rexml' # add missing gem due to ruby3 (https://github.com/Shopify/bootsnap/issues/325)
|
gem 'rexml' # add missing gem due to ruby3 (https://github.com/Shopify/bootsnap/issues/325)
|
||||||
gem 'rgeo-geojson'
|
gem 'rgeo-geojson'
|
||||||
gem 'rqrcode'
|
gem 'rqrcode'
|
||||||
|
|
13
Gemfile.lock
13
Gemfile.lock
|
@ -121,10 +121,6 @@ GEM
|
||||||
axlsx_styler (1.1.0)
|
axlsx_styler (1.1.0)
|
||||||
activesupport (>= 3.1)
|
activesupport (>= 3.1)
|
||||||
caxlsx (>= 2.0.2)
|
caxlsx (>= 2.0.2)
|
||||||
babel-source (5.8.35)
|
|
||||||
babel-transpiler (0.7.0)
|
|
||||||
babel-source (>= 4.0, < 6)
|
|
||||||
execjs (~> 2.0)
|
|
||||||
bcrypt (3.1.16)
|
bcrypt (3.1.16)
|
||||||
better_html (1.0.16)
|
better_html (1.0.16)
|
||||||
actionview (>= 4.0)
|
actionview (>= 4.0)
|
||||||
|
@ -173,7 +169,6 @@ GEM
|
||||||
coercible (1.0.0)
|
coercible (1.0.0)
|
||||||
descendants_tracker (~> 0.0.1)
|
descendants_tracker (~> 0.0.1)
|
||||||
concurrent-ruby (1.1.10)
|
concurrent-ruby (1.1.10)
|
||||||
connection_pool (2.2.3)
|
|
||||||
content_disposition (1.0.0)
|
content_disposition (1.0.0)
|
||||||
crack (0.4.5)
|
crack (0.4.5)
|
||||||
rexml
|
rexml
|
||||||
|
@ -238,7 +233,6 @@ GEM
|
||||||
ethon (0.15.0)
|
ethon (0.15.0)
|
||||||
ffi (>= 1.15.0)
|
ffi (>= 1.15.0)
|
||||||
excon (0.79.0)
|
excon (0.79.0)
|
||||||
execjs (2.7.0)
|
|
||||||
factory_bot (6.1.0)
|
factory_bot (6.1.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
faraday (1.8.0)
|
faraday (1.8.0)
|
||||||
|
@ -572,12 +566,6 @@ GEM
|
||||||
rb-fsevent (0.10.4)
|
rb-fsevent (0.10.4)
|
||||||
rb-inotify (0.10.1)
|
rb-inotify (0.10.1)
|
||||||
ffi (~> 1.0)
|
ffi (~> 1.0)
|
||||||
react-rails (2.6.1)
|
|
||||||
babel-transpiler (>= 0.7.0)
|
|
||||||
connection_pool
|
|
||||||
execjs
|
|
||||||
railties (>= 3.2)
|
|
||||||
tilt
|
|
||||||
regexp_parser (2.1.0)
|
regexp_parser (2.1.0)
|
||||||
request_store (1.5.0)
|
request_store (1.5.0)
|
||||||
rack (>= 1.4)
|
rack (>= 1.4)
|
||||||
|
@ -885,7 +873,6 @@ DEPENDENCIES
|
||||||
rails-erd
|
rails-erd
|
||||||
rails-i18n
|
rails-i18n
|
||||||
rake-progressbar
|
rake-progressbar
|
||||||
react-rails
|
|
||||||
rexml
|
rexml
|
||||||
rgeo-geojson
|
rgeo-geojson
|
||||||
rqrcode
|
rqrcode
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form [data-react-class='MapEditor'] [data-reach-combobox-input] {
|
.form [data-react-component-value='MapEditor'] [data-reach-combobox-input] {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -488,13 +488,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-react-class]:not([data-react-class^="ComboMultiple"]) {
|
[data-react-component-value]:not([data-react-component-value^="ComboMultiple"]) {
|
||||||
[data-reach-combobox-input]:not(.no-margin) {
|
[data-reach-combobox-input]:not(.no-margin) {
|
||||||
margin-bottom: $default-fields-spacer;
|
margin-bottom: $default-fields-spacer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-react-class^="ComboMultiple"] {
|
[data-react-component-value^="ComboMultiple"] {
|
||||||
margin-bottom: $default-fields-spacer;
|
margin-bottom: $default-fields-spacer;
|
||||||
|
|
||||||
[data-reach-combobox-input] {
|
[data-reach-combobox-input] {
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-react-class^="ComboMultiple"] {
|
[data-react-component-value^="ComboMultiple"] {
|
||||||
margin-bottom: $default-fields-spacer;
|
margin-bottom: $default-fields-spacer;
|
||||||
|
|
||||||
[data-reach-combobox-token-list] {
|
[data-reach-combobox-token-list] {
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-react-class^="ComboMultiple"] {
|
[data-react-component-value^="ComboMultiple"] {
|
||||||
margin-bottom: $default-fields-spacer;
|
margin-bottom: $default-fields-spacer;
|
||||||
|
|
||||||
[data-reach-combobox-token-list] {
|
[data-reach-combobox-token-list] {
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
module ApplicationHelper
|
module ApplicationHelper
|
||||||
include SanitizeUrl
|
include SanitizeUrl
|
||||||
|
|
||||||
|
def html_lang
|
||||||
|
I18n.locale.to_s
|
||||||
|
end
|
||||||
|
|
||||||
def sanitize_url(url)
|
def sanitize_url(url)
|
||||||
if !url.nil?
|
if !url.nil?
|
||||||
super(url, schemes: ['http', 'https'], replace_evil_with: root_url)
|
super(url, schemes: ['http', 'https'], replace_evil_with: root_url)
|
||||||
|
@ -26,6 +30,10 @@ module ApplicationHelper
|
||||||
class_names.join(' ')
|
class_names.join(' ')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def react_component(name, props = {}, html = {})
|
||||||
|
tag.div(**html.merge(data: { controller: 'react', react_component_value: name, react_props_value: props.to_json }))
|
||||||
|
end
|
||||||
|
|
||||||
def render_to_element(selector, partial:, outer: false, locals: {})
|
def render_to_element(selector, partial:, outer: false, locals: {})
|
||||||
method = outer ? 'outerHTML' : 'innerHTML'
|
method = outer ? 'outerHTML' : 'innerHTML'
|
||||||
html = escape_javascript(render partial: partial, locals: locals)
|
html = escape_javascript(render partial: partial, locals: locals)
|
||||||
|
|
70
app/javascript/controllers/react_controller.tsx
Normal file
70
app/javascript/controllers/react_controller.tsx
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import { Controller } from '@hotwired/stimulus';
|
||||||
|
import React, { lazy, Suspense, FunctionComponent, StrictMode } from 'react';
|
||||||
|
import { render, unmountComponentAtNode } from 'react-dom';
|
||||||
|
import invariant from 'tiny-invariant';
|
||||||
|
|
||||||
|
type Props = Record<string, unknown>;
|
||||||
|
type Loader = () => Promise<{ default: FunctionComponent<Props> }>;
|
||||||
|
const componentsRegistry = new Map<string, FunctionComponent<Props>>();
|
||||||
|
|
||||||
|
export function registerComponents(components: Record<string, Loader>): void {
|
||||||
|
for (const [className, loader] of Object.entries(components)) {
|
||||||
|
componentsRegistry.set(className, LoadableComponent(loader));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize React components when their markup appears into the DOM.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// <div data-controller="react" data-react-component-value="ComboMultiple" data-react-props-value="{}"></div>
|
||||||
|
//
|
||||||
|
export class ReactController extends Controller {
|
||||||
|
static values = {
|
||||||
|
component: String,
|
||||||
|
props: Object
|
||||||
|
};
|
||||||
|
|
||||||
|
declare readonly componentValue: string;
|
||||||
|
declare readonly propsValue: Props;
|
||||||
|
|
||||||
|
connect(): void {
|
||||||
|
this.mountComponent(this.element as HTMLElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect(): void {
|
||||||
|
unmountComponentAtNode(this.element as HTMLElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
private mountComponent(node: HTMLElement): void {
|
||||||
|
const componentName = this.componentValue;
|
||||||
|
const props = this.propsValue;
|
||||||
|
const Component = this.getComponent(componentName);
|
||||||
|
|
||||||
|
invariant(
|
||||||
|
Component,
|
||||||
|
`Cannot find a React component with class "${componentName}"`
|
||||||
|
);
|
||||||
|
render(
|
||||||
|
<StrictMode>
|
||||||
|
<Component {...props} />
|
||||||
|
</StrictMode>,
|
||||||
|
node
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getComponent(componentName: string): FunctionComponent<Props> | null {
|
||||||
|
return componentsRegistry.get(componentName) ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Spinner = () => <div className="spinner left" />;
|
||||||
|
|
||||||
|
function LoadableComponent(loader: Loader): FunctionComponent<Props> {
|
||||||
|
const LazyComponent = lazy(loader);
|
||||||
|
const Component: FunctionComponent<Props> = (props: Props) => (
|
||||||
|
<Suspense fallback={<Spinner />}>
|
||||||
|
<LazyComponent {...props} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
return Component;
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import '../shared/polyfills';
|
||||||
import Rails from '@rails/ujs';
|
import Rails from '@rails/ujs';
|
||||||
import * as ActiveStorage from '@rails/activestorage';
|
import * as ActiveStorage from '@rails/activestorage';
|
||||||
import 'whatwg-fetch'; // window.fetch polyfill
|
import 'whatwg-fetch'; // window.fetch polyfill
|
||||||
|
import { Application } from '@hotwired/stimulus';
|
||||||
|
|
||||||
import '../shared/page-update-event';
|
import '../shared/page-update-event';
|
||||||
import '../shared/activestorage/ujs';
|
import '../shared/activestorage/ujs';
|
||||||
|
@ -12,6 +13,11 @@ import '../shared/franceconnect';
|
||||||
import '../shared/toggle-target';
|
import '../shared/toggle-target';
|
||||||
import '../shared/ujs-error-handling';
|
import '../shared/ujs-error-handling';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ReactController,
|
||||||
|
registerComponents
|
||||||
|
} from '../controllers/react_controller';
|
||||||
|
|
||||||
import '../new_design/dropdown';
|
import '../new_design/dropdown';
|
||||||
import '../new_design/form-validation';
|
import '../new_design/form-validation';
|
||||||
import '../new_design/procedure-context';
|
import '../new_design/procedure-context';
|
||||||
|
@ -46,37 +52,23 @@ import {
|
||||||
showNewAccountPasswordConfirmation
|
showNewAccountPasswordConfirmation
|
||||||
} from '../new_design/fc-fusion';
|
} from '../new_design/fc-fusion';
|
||||||
|
|
||||||
import {
|
registerComponents({
|
||||||
registerReactComponents,
|
Chartkick: () => import('../components/Chartkick'),
|
||||||
Loadable
|
ComboAdresseSearch: () => import('../components/ComboAdresseSearch'),
|
||||||
} from '../shared/register-react-components';
|
ComboAnnuaireEducationSearch: () =>
|
||||||
|
import('../components/ComboAnnuaireEducationSearch'),
|
||||||
registerReactComponents({
|
ComboCommunesSearch: () => import('../components/ComboCommunesSearch'),
|
||||||
Chartkick: Loadable(() => import('../components/Chartkick')),
|
ComboDepartementsSearch: () =>
|
||||||
ComboAdresseSearch: Loadable(() =>
|
import('../components/ComboDepartementsSearch'),
|
||||||
import('../components/ComboAdresseSearch')
|
ComboMultipleDropdownList: () =>
|
||||||
),
|
import('../components/ComboMultipleDropdownList'),
|
||||||
ComboAnnuaireEducationSearch: Loadable(() =>
|
ComboMultiple: () => import('../components/ComboMultiple'),
|
||||||
import('../components/ComboAnnuaireEducationSearch')
|
ComboPaysSearch: () => import('../components/ComboPaysSearch'),
|
||||||
),
|
ComboRegionsSearch: () => import('../components/ComboRegionsSearch'),
|
||||||
ComboCommunesSearch: Loadable(() =>
|
MapEditor: () => import('../components/MapEditor'),
|
||||||
import('../components/ComboCommunesSearch')
|
MapReader: () => import('../components/MapReader'),
|
||||||
),
|
Trix: () => import('../components/Trix'),
|
||||||
ComboDepartementsSearch: Loadable(() =>
|
TypesDeChampEditor: () => import('../components/TypesDeChampEditor')
|
||||||
import('../components/ComboDepartementsSearch')
|
|
||||||
),
|
|
||||||
ComboMultipleDropdownList: Loadable(() =>
|
|
||||||
import('../components/ComboMultipleDropdownList')
|
|
||||||
),
|
|
||||||
ComboMultiple: Loadable(() => import('../components/ComboMultiple')),
|
|
||||||
ComboPaysSearch: Loadable(() => import('../components/ComboPaysSearch')),
|
|
||||||
ComboRegionsSearch: Loadable(() =>
|
|
||||||
import('../components/ComboRegionsSearch')
|
|
||||||
),
|
|
||||||
MapEditor: Loadable(() => import('../components/MapEditor')),
|
|
||||||
MapReader: Loadable(() => import('../components/MapReader')),
|
|
||||||
Trix: Loadable(() => import('../components/Trix')),
|
|
||||||
TypesDeChampEditor: Loadable(() => import('../components/TypesDeChampEditor'))
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// This is the global application namespace where we expose helpers used from rails views
|
// This is the global application namespace where we expose helpers used from rails views
|
||||||
|
@ -98,5 +90,8 @@ const DS = {
|
||||||
Rails.start();
|
Rails.start();
|
||||||
ActiveStorage.start();
|
ActiveStorage.start();
|
||||||
|
|
||||||
|
const Stimulus = Application.start();
|
||||||
|
Stimulus.register('react', ReactController);
|
||||||
|
|
||||||
// Expose globals
|
// Expose globals
|
||||||
window.DS = window.DS || DS;
|
window.DS = window.DS || DS;
|
||||||
|
|
|
@ -1,97 +0,0 @@
|
||||||
import React, { Suspense, lazy, createElement, ComponentClass } from 'react';
|
|
||||||
import { render } from 'react-dom';
|
|
||||||
|
|
||||||
// This attribute holds the name of component which should be mounted
|
|
||||||
// example: `data-react-class="MyApp.Items.EditForm"`
|
|
||||||
const CLASS_NAME_ATTR = 'data-react-class';
|
|
||||||
|
|
||||||
// This attribute holds JSON stringified props for initializing the component
|
|
||||||
// example: `data-react-props="{\"item\": { \"id\": 1, \"name\": \"My Item\"} }"`
|
|
||||||
const PROPS_ATTR = 'data-react-props';
|
|
||||||
|
|
||||||
const CLASS_NAME_SELECTOR = `[${CLASS_NAME_ATTR}]`;
|
|
||||||
|
|
||||||
// helper method for the mount and unmount methods to find the
|
|
||||||
// `data-react-class` DOM elements
|
|
||||||
function findDOMNodes(searchSelector?: string): NodeListOf<HTMLDivElement> {
|
|
||||||
const [selector, parent] = getSelector(searchSelector);
|
|
||||||
return parent.querySelectorAll<HTMLDivElement>(selector);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSelector(searchSelector?: string): [string, Document] {
|
|
||||||
switch (typeof searchSelector) {
|
|
||||||
case 'undefined':
|
|
||||||
return [CLASS_NAME_SELECTOR, document];
|
|
||||||
case 'object':
|
|
||||||
return [CLASS_NAME_SELECTOR, searchSelector];
|
|
||||||
case 'string':
|
|
||||||
return [
|
|
||||||
['', ' ']
|
|
||||||
.map(
|
|
||||||
(separator) => `${searchSelector}${separator}${CLASS_NAME_SELECTOR}`
|
|
||||||
)
|
|
||||||
.join(', '),
|
|
||||||
document
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ReactComponentRegistry {
|
|
||||||
#components;
|
|
||||||
|
|
||||||
constructor(components: Record<string, ComponentClass>) {
|
|
||||||
this.#components = components;
|
|
||||||
}
|
|
||||||
|
|
||||||
getConstructor(className: string | null) {
|
|
||||||
return className ? this.#components[className] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
mountComponents(searchSelector?: string) {
|
|
||||||
const nodes = findDOMNodes(searchSelector);
|
|
||||||
|
|
||||||
for (const node of nodes) {
|
|
||||||
const className = node.getAttribute(CLASS_NAME_ATTR);
|
|
||||||
const ComponentClass = this.getConstructor(className);
|
|
||||||
const propsJson = node.getAttribute(PROPS_ATTR);
|
|
||||||
const props = propsJson && JSON.parse(propsJson);
|
|
||||||
|
|
||||||
if (!ComponentClass) {
|
|
||||||
const message = `Cannot find component: "${className}"`;
|
|
||||||
console?.log(
|
|
||||||
`%c[react-rails] %c${message} for element`,
|
|
||||||
'font-weight: bold',
|
|
||||||
'',
|
|
||||||
node
|
|
||||||
);
|
|
||||||
throw new Error(
|
|
||||||
`${message}. Make sure your component is available to render.`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
render(createElement(ComponentClass, props), node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Loader = () => <div className="spinner left" />;
|
|
||||||
|
|
||||||
export function Loadable(loader: () => Promise<{ default: ComponentClass }>) {
|
|
||||||
const LazyComponent = lazy(loader);
|
|
||||||
|
|
||||||
return function PureComponent(props: Record<string, unknown>) {
|
|
||||||
return (
|
|
||||||
<Suspense fallback={<Loader />}>
|
|
||||||
<LazyComponent {...props} />
|
|
||||||
</Suspense>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function registerReactComponents(
|
|
||||||
components: Record<string, ComponentClass>
|
|
||||||
) {
|
|
||||||
const registry = new ReactComponentRegistry(components);
|
|
||||||
|
|
||||||
addEventListener('ds:page:update', () => registry.mountComponents());
|
|
||||||
}
|
|
|
@ -5,37 +5,44 @@
|
||||||
html: { class: 'form' } do |f|
|
html: { class: 'form' } do |f|
|
||||||
|
|
||||||
= f.label :routing_criteria_name do
|
= f.label :routing_criteria_name do
|
||||||
Libellé du routage
|
= t('.routing.title')
|
||||||
%p.notice Ce texte apparaitra sur le formulaire usager comme le libellé d’une liste
|
%p.notice
|
||||||
= f.text_field :routing_criteria_name, placeholder: 'ex. Votre ville', required: true
|
= t('.routing.notice')
|
||||||
= f.submit 'Renommer', class: 'button primary send'
|
= f.text_field :routing_criteria_name, placeholder: t('.add_a_group.placeholder'), required: true
|
||||||
|
= f.submit t('.button.rename'), class: 'button primary send'
|
||||||
|
|
||||||
.card
|
.card
|
||||||
.card-title Gestion des Groupes
|
.card-title
|
||||||
|
= t('.group_management.title')
|
||||||
|
|
||||||
= form_for :groupe_instructeur, html: { class: 'form' } do |f|
|
= form_for :groupe_instructeur, html: { class: 'form' } do |f|
|
||||||
= f.label :label do
|
= f.label :label do
|
||||||
Ajouter un groupe
|
= t('.add_a_group.title')
|
||||||
%p.notice Ce groupe sera un choix de la liste « #{procedure.routing_criteria_name} » .
|
%p.notice
|
||||||
= f.text_field :label, placeholder: 'ex. Ville de Bordeaux', required: true
|
= t('.add_a_group.notice', routing_criteria_name: procedure.routing_criteria_name)
|
||||||
= f.submit 'Ajouter le groupe', class: "button primary send"
|
= f.text_field :label, placeholder: t('.add_a_group.placeholder'), required: true
|
||||||
|
= f.submit t('.button.add_group'), class: "button primary send"
|
||||||
|
|
||||||
- csv_max_size = Administrateurs::GroupeInstructeursController::CSV_MAX_SIZE
|
- csv_max_size = Administrateurs::GroupeInstructeursController::CSV_MAX_SIZE
|
||||||
- if procedure.publiee?
|
- if procedure.publiee?
|
||||||
= form_tag import_admin_procedure_groupe_instructeurs_path(procedure), method: :post, multipart: true, class: "mt-4 form" do
|
= form_tag import_admin_procedure_groupe_instructeurs_path(procedure), method: :post, multipart: true, class: "mt-4 form" do
|
||||||
= label_tag "Importer par fichier CSV"
|
= label_tag t('.csv_import.title')
|
||||||
%p.notice Le fichier csv doit comporter 2 colonnes (Groupe, Email) et être séparé par des virgules. L'import n'écrase pas les groupes et les instructeurs existants.
|
%p.notice
|
||||||
%p.notice Le poids du fichier doit être inférieur à #{number_to_human_size(csv_max_size)}
|
= t('.csv_import.notice_1')
|
||||||
%p.mt-2.mb-2= link_to "Télécharger l'exemple de fichier CSV", "/csv/#{I18n.locale}/import-groupe-test.csv"
|
%p.notice
|
||||||
|
= t('.csv_import.notice_2', csv_max_size: number_to_human_size(csv_max_size))
|
||||||
|
%p.mt-2.mb-2= link_to t('.csv_import.download_exemple'), "/csv/#{I18n.locale}/import-groupe-test.csv"
|
||||||
= file_field_tag :group_csv_file, required: true, accept: 'text/csv', size: "1"
|
= file_field_tag :group_csv_file, required: true, accept: 'text/csv', size: "1"
|
||||||
= submit_tag "Importer le fichier", class: 'button primary send', data: { disable_with: "Envoi..." }
|
= submit_tag t('.csv_import.import_file'), class: 'button primary send', data: { disable_with: "Envoi..." }
|
||||||
- else
|
- else
|
||||||
%p.mt-4.form.bold.mb-2.text-lg Importer par fichier CSV
|
%p.mt-4.form.bold.mb-2.text-lg
|
||||||
%p.notice L’import d’instructeurs par fichier CSV est disponible une fois la démarche publiée
|
= t('.csv_import.title')
|
||||||
|
%p.notice
|
||||||
|
= t('.csv_import.import_file_procedure_not_published')
|
||||||
%table.table.mt-2
|
%table.table.mt-2
|
||||||
%thead
|
%thead
|
||||||
%tr
|
%tr
|
||||||
|
// i18n-tasks-use t('.existing_groupe')
|
||||||
%th{ colspan: 2 }= t(".existing_groupe", count: groupes_instructeurs.total_count)
|
%th{ colspan: 2 }= t(".existing_groupe", count: groupes_instructeurs.total_count)
|
||||||
%th
|
%th
|
||||||
- if groupe_instructeurs_count > 1
|
- if groupe_instructeurs_count > 1
|
||||||
|
@ -44,18 +51,18 @@
|
||||||
- groupes_instructeurs.each do |group|
|
- groupes_instructeurs.each do |group|
|
||||||
%tr
|
%tr
|
||||||
%td= group.label
|
%td= group.label
|
||||||
%td.actions= link_to "voir", admin_procedure_groupe_instructeur_path(procedure, group)
|
%td.actions= link_to t('.view'), admin_procedure_groupe_instructeur_path(procedure, group)
|
||||||
- if groupes_instructeurs.many?
|
- if groupes_instructeurs.many?
|
||||||
- if group.dossiers.empty?
|
- if group.dossiers.empty?
|
||||||
%td.actions
|
%td.actions
|
||||||
= link_to admin_procedure_groupe_instructeur_path(procedure, group), { method: :delete, class: 'button', data: { confirm: "Êtes-vous sûr de vouloir supprimer le groupe « #{group.label} » ?" }} do
|
= link_to admin_procedure_groupe_instructeur_path(procedure, group), { method: :delete, class: 'button', data: { confirm: t('.group_management.delete_confirmation', group_name: group.label) }} do
|
||||||
%span.icon.delete
|
%span.icon.delete
|
||||||
supprimer ce groupe
|
= t('.group_management.delete')
|
||||||
- else
|
- else
|
||||||
%td.actions
|
%td.actions
|
||||||
= link_to reaffecter_dossiers_admin_procedure_groupe_instructeur_path(procedure, group), class: 'button', title:'Réaffecter les dossiers à un autre groupe afin de pouvoir le supprimer' do
|
= link_to reaffecter_dossiers_admin_procedure_groupe_instructeur_path(procedure, group), class: 'button', title: t('.group_management.move_folders_confirmation') do
|
||||||
%span.icon.follow
|
%span.icon.follow
|
||||||
déplacer les dossiers
|
= t('.group_management.move_folders')
|
||||||
|
|
||||||
|
|
||||||
= paginate groupes_instructeurs
|
= paginate groupes_instructeurs
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
.card
|
.card
|
||||||
.card-title Routage
|
.card-title
|
||||||
|
= t('.title')
|
||||||
- if !procedure.routee?
|
- if !procedure.routee?
|
||||||
%p.notice= t('.notice_html')
|
%p.notice= t('.notice_html')
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
- if @procedure.routee?
|
- if @procedure.routee?
|
||||||
= render partial: 'administrateurs/breadcrumbs',
|
= render partial: 'administrateurs/breadcrumbs',
|
||||||
locals: { steps: [link_to('Démarches', admin_procedures_path),
|
locals: { steps: [link_to(t('.procedures'), admin_procedures_path),
|
||||||
link_to(@procedure.libelle, admin_procedure_path(@procedure)),
|
link_to(@procedure.libelle, admin_procedure_path(@procedure)),
|
||||||
'Groupes d’instructeurs'] }
|
t('.instructors_group')] }
|
||||||
- else
|
- else
|
||||||
= render partial: 'administrateurs/breadcrumbs',
|
= render partial: 'administrateurs/breadcrumbs',
|
||||||
locals: { steps: [link_to('Démarches', admin_procedures_path),
|
locals: { steps: [link_to('Démarches', admin_procedures_path),
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
.card-title Réaffectation des dossiers du groupe « #{@groupe_instructeur.label} »
|
.card-title Réaffectation des dossiers du groupe « #{@groupe_instructeur.label} »
|
||||||
%p
|
%p
|
||||||
Le groupe « #{@groupe_instructeur.label} » contient des dossiers. Afin de procéder à sa suppression, vous devez réaffecter ses dossiers à un autre groupe instructeur.
|
Le groupe « #{@groupe_instructeur.label} » contient des dossiers. Afin de procéder à sa suppression, vous devez réaffecter ses dossiers à un autre groupe instructeur.
|
||||||
|
|
||||||
%table.table.mt-2
|
%table.table.mt-2
|
||||||
%thead
|
%thead
|
||||||
%tr
|
%tr
|
||||||
|
|
|
@ -5,74 +5,75 @@
|
||||||
'Notifications'] }
|
'Notifications'] }
|
||||||
|
|
||||||
.container
|
.container
|
||||||
%h1 Notifications par email
|
%h1
|
||||||
|
= t('.title')
|
||||||
|
|
||||||
= form_for @assign_to, url: update_email_notifications_instructeur_procedure_path(@procedure), html: { class: 'form' } do |form|
|
= form_for @assign_to, url: update_email_notifications_instructeur_procedure_path(@procedure), html: { class: 'form' } do |form|
|
||||||
.explication
|
.explication
|
||||||
Configurez sur cette page les notifications que vous souhaitez recevoir par email pour cette démarche.
|
= t('.subtitle')
|
||||||
|
|
||||||
= form.label :email_notification, "Recevoir une notification à chaque dossier déposé"
|
= form.label :email_notification, t('.for_each_file_submitted.title')
|
||||||
|
|
||||||
%p.notice
|
%p.notice
|
||||||
Cet email vous signale le dépôt d’un nouveau dossier.
|
= t('.for_each_file_submitted.notice_1')
|
||||||
%p.notice
|
%p.notice
|
||||||
Il est envoyé à chaque fois qu'un usager dépose un dossier.
|
= t('.for_each_file_submitted.notice_2')
|
||||||
|
|
||||||
.radios
|
.radios
|
||||||
%label
|
%label
|
||||||
= form.radio_button :instant_email_dossier_notifications_enabled, true
|
= form.radio_button :instant_email_dossier_notifications_enabled, true
|
||||||
Oui
|
= t('.utils.positive')
|
||||||
|
|
||||||
%label
|
%label
|
||||||
= form.radio_button :instant_email_dossier_notifications_enabled, false
|
= form.radio_button :instant_email_dossier_notifications_enabled, false
|
||||||
Non
|
= t('.utils.negative')
|
||||||
|
|
||||||
= form.label :email_notification, "Recevoir une notification à chaque message déposé"
|
= form.label :email_notification, t('.for_each_message_submitted.title')
|
||||||
|
|
||||||
%p.notice
|
%p.notice
|
||||||
Cet email vous signale le dépôt d’un nouveau message sur vos dossiers suivis.
|
= t('.for_each_message_submitted.notice_1')
|
||||||
%p.notice
|
%p.notice
|
||||||
Il est envoyé à chaque fois qu'un usager dépose un message.
|
= t('.for_each_message_submitted.notice_2')
|
||||||
|
|
||||||
.radios
|
.radios
|
||||||
%label
|
%label
|
||||||
= form.radio_button :instant_email_message_notifications_enabled, true
|
= form.radio_button :instant_email_message_notifications_enabled, true
|
||||||
Oui
|
= t('.utils.positive')
|
||||||
|
|
||||||
%label
|
%label
|
||||||
= form.radio_button :instant_email_message_notifications_enabled, false
|
= form.radio_button :instant_email_message_notifications_enabled, false
|
||||||
Non
|
= t('.utils.negative')
|
||||||
|
|
||||||
= form.label :email_notification, "Recevoir une notification quotidienne"
|
= form.label :email_notification, t('.daily_notifications.title')
|
||||||
|
|
||||||
%p.notice
|
%p.notice
|
||||||
Cet email vous signale le dépôt de nouveaux dossiers sur cette démarche, ou des changements sur vos dossiers suivis.
|
= t('.daily_notifications.notice_1')
|
||||||
%p.notice
|
%p.notice
|
||||||
Il est envoyé une fois par jour, du lundi au samedi, vers 10 h du matin.
|
= t('.daily_notifications.notice_2')
|
||||||
|
|
||||||
.radios
|
.radios
|
||||||
%label
|
%label
|
||||||
= form.radio_button :daily_email_notifications_enabled, true
|
= form.radio_button :daily_email_notifications_enabled, true
|
||||||
Oui
|
= t('.utils.positive')
|
||||||
|
|
||||||
%label
|
%label
|
||||||
= form.radio_button :daily_email_notifications_enabled, false
|
= form.radio_button :daily_email_notifications_enabled, false
|
||||||
Non
|
= t('.utils.negative')
|
||||||
|
|
||||||
= form.label :email_notification, "Recevoir un récapitulatif hebdomadaire"
|
= form.label :email_notification, t('.hebdo_recap.title')
|
||||||
%p.notice
|
%p.notice
|
||||||
Cet email récapitule l’activité de la semaine sur l’ensemble de vos démarches.
|
= t('.hebdo_recap.notice_1')
|
||||||
%p.notice
|
%p.notice
|
||||||
Il est envoyé chaque semaine le lundi matin.
|
= t('.hebdo_recap.notice_2')
|
||||||
|
|
||||||
.radios
|
.radios
|
||||||
%label
|
%label
|
||||||
= form.radio_button :weekly_email_notifications_enabled, true
|
= form.radio_button :weekly_email_notifications_enabled, true
|
||||||
Oui
|
= t('.utils.positive')
|
||||||
%label
|
%label
|
||||||
= form.radio_button :weekly_email_notifications_enabled, false
|
= form.radio_button :weekly_email_notifications_enabled, false
|
||||||
Non
|
= t('.utils.negative')
|
||||||
|
|
||||||
.send-wrapper
|
.send-wrapper
|
||||||
= link_to "Revenir à la procédure", instructeur_procedure_path(@procedure), class: 'button mr-1'
|
= link_to t('.buttons.back_to_procedure'), instructeur_procedure_path(@procedure), class: 'button mr-1'
|
||||||
= form.submit "Enregistrer", class: "button primary"
|
= form.submit t('.buttons.save'), class: "button primary"
|
||||||
|
|
|
@ -2,10 +2,12 @@
|
||||||
|
|
||||||
= render partial: 'administrateurs/breadcrumbs',
|
= render partial: 'administrateurs/breadcrumbs',
|
||||||
locals: { steps: [link_to(@procedure.libelle, instructeur_procedure_path(@procedure)),
|
locals: { steps: [link_to(@procedure.libelle, instructeur_procedure_path(@procedure)),
|
||||||
'Contacter les usagers (brouillon)'] }
|
t('.contact_users')] }
|
||||||
.messagerie.container
|
.messagerie.container
|
||||||
- if @email_usagers_dossiers.present?
|
- if @email_usagers_dossiers.present?
|
||||||
%p.notice.mb-2.mt-4 Vous allez envoyer un message à #{pluralize(@dossiers_count, 'personne')} dont les dossiers sont en brouillon, dans les groupes instructeurs : #{@groupe_instructeurs.join(', ')}.
|
%p.notice.mb-2.mt-4
|
||||||
|
= t('.notice', dossiers_count: pluralize(@dossiers_count, 'personne'), groupe_instructeurs: @groupe_instructeurs.join(', '))
|
||||||
|
|
||||||
= render partial: 'shared/dossiers/messages/form', locals: { commentaire: @commentaire, form_url: create_multiple_commentaire_instructeur_procedure_path(@procedure), disable_piece_jointe: true }
|
= render partial: 'shared/dossiers/messages/form', locals: { commentaire: @commentaire, form_url: create_multiple_commentaire_instructeur_procedure_path(@procedure), disable_piece_jointe: true }
|
||||||
|
|
||||||
- if @bulk_messages.present?
|
- if @bulk_messages.present?
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
|
|
||||||
= render partial: 'administrateurs/breadcrumbs',
|
= render partial: 'administrateurs/breadcrumbs',
|
||||||
locals: { steps: [link_to(@procedure.libelle, instructeur_procedure_path(@procedure)),
|
locals: { steps: [link_to(@procedure.libelle, instructeur_procedure_path(@procedure)),
|
||||||
'Statistiques'] }
|
t('.title')] }
|
||||||
|
|
||||||
= render partial: 'shared/procedures/stats', locals: { title: title }
|
= render partial: 'shared/procedures/stats', locals: { title: title }
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
- if nav_bar_profile == :user
|
- if nav_bar_profile == :user
|
||||||
%ul.header-tabs
|
%ul.header-tabs
|
||||||
%li
|
%li
|
||||||
= active_link_to "Dossiers", dossiers_path, active: :inclusive, class: 'tab-link'
|
= active_link_to t('.files'), dossiers_path, active: :inclusive, class: 'tab-link'
|
||||||
- if current_user.expert && current_expert.avis_summary[:total] > 0
|
- if current_user.expert && current_expert.avis_summary[:total] > 0
|
||||||
= render partial: 'layouts/header/avis_tab', locals: { current_expert: current_expert }
|
= render partial: 'layouts/header/avis_tab', locals: { current_expert: current_expert }
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
!!! 5
|
!!! 5
|
||||||
%html{ lang: "fr", class: yield(:root_class) }
|
%html{ lang: html_lang, class: yield(:root_class) }
|
||||||
%head
|
%head
|
||||||
%meta{ "http-equiv": "Content-Type", content: "text/html; charset=UTF-8" }
|
%meta{ "http-equiv": "Content-Type", content: "text/html; charset=UTF-8" }
|
||||||
%meta{ "http-equiv": "X-UA-Compatible", content: "IE=edge" }
|
%meta{ "http-equiv": "X-UA-Compatible", content: "IE=edge" }
|
||||||
|
|
|
@ -2,5 +2,7 @@
|
||||||
= mail_to CONTACT_EMAIL do
|
= mail_to CONTACT_EMAIL do
|
||||||
%span.icon.mail
|
%span.icon.mail
|
||||||
.dropdown-description
|
.dropdown-description
|
||||||
%span.help-dropdown-title Contact technique
|
%span.help-dropdown-title
|
||||||
%p Envoyez nous un message à #{CONTACT_EMAIL}.
|
= t('help_dropdown.technical_contact_title')
|
||||||
|
%p
|
||||||
|
= t('help_dropdown.technical_contact_description', contact_email: CONTACT_EMAIL)
|
||||||
|
|
|
@ -2,5 +2,8 @@
|
||||||
= link_to FAQ_URL, target: "_blank", rel: "noopener" do
|
= link_to FAQ_URL, target: "_blank", rel: "noopener" do
|
||||||
%span.icon.help
|
%span.icon.help
|
||||||
.dropdown-description
|
.dropdown-description
|
||||||
%span.help-dropdown-title Un problème avec le site ?
|
%span.help-dropdown-title
|
||||||
%p Trouvez votre réponse dans l’aide en ligne.
|
= t('help_dropdown.problem_title')
|
||||||
|
%p
|
||||||
|
= t('help_dropdown.problem_description')
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ module.exports = function (api) {
|
||||||
'@babel/plugin-syntax-dynamic-import',
|
'@babel/plugin-syntax-dynamic-import',
|
||||||
isTestEnv && 'babel-plugin-dynamic-import-node',
|
isTestEnv && 'babel-plugin-dynamic-import-node',
|
||||||
'@babel/plugin-transform-destructuring',
|
'@babel/plugin-transform-destructuring',
|
||||||
|
['@babel/plugin-transform-typescript', { allowDeclareFields: true }],
|
||||||
[
|
[
|
||||||
'@babel/plugin-proposal-class-properties',
|
'@babel/plugin-proposal-class-properties',
|
||||||
{
|
{
|
||||||
|
|
|
@ -34,6 +34,11 @@ en:
|
||||||
custom_message: 'If you are a human, ignore this field'
|
custom_message: 'If you are a human, ignore this field'
|
||||||
|
|
||||||
help: 'Help'
|
help: 'Help'
|
||||||
|
help_dropdown:
|
||||||
|
problem_title: A problem with the website ?
|
||||||
|
problem_description: Find your answer in the online help.
|
||||||
|
technical_contact_title: Technical contact
|
||||||
|
technical_contact_description: Send us a message to %{contact_email}.
|
||||||
utils:
|
utils:
|
||||||
'yes': Yes
|
'yes': Yes
|
||||||
'no': No
|
'no': No
|
||||||
|
|
|
@ -22,8 +22,12 @@
|
||||||
fr:
|
fr:
|
||||||
invisible_captcha:
|
invisible_captcha:
|
||||||
custom_message: 'Si vous êtes un humain, veuillez ignorer ce champs'
|
custom_message: 'Si vous êtes un humain, veuillez ignorer ce champs'
|
||||||
|
|
||||||
help: 'Aide'
|
help: 'Aide'
|
||||||
|
help_dropdown:
|
||||||
|
problem_title: Un problème avec le site ?
|
||||||
|
problem_description: Trouvez votre réponse dans l’aide en ligne.
|
||||||
|
technical_contact_title: Contact technique
|
||||||
|
technical_contact_description: Envoyez nous un message à %{contact_email}.
|
||||||
utils:
|
utils:
|
||||||
'yes': Oui
|
'yes': Oui
|
||||||
'no': Non
|
'no': Non
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
fr:
|
en:
|
||||||
activerecord:
|
activerecord:
|
||||||
attributes:
|
attributes:
|
||||||
procedure_presentation:
|
procedure_presentation:
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
en:
|
||||||
|
administrateurs:
|
||||||
|
experts_procedures:
|
||||||
|
create:
|
||||||
|
experts_assignment:
|
||||||
|
one: "The expert %{value} was assigned to the procedure n° %{procedure}"
|
||||||
|
other: "The experts %{value} were assigned to the procedure n° %{procedure}"
|
||||||
|
groupe_instructeurs:
|
||||||
|
index:
|
||||||
|
procedures: Procedures
|
||||||
|
instructors_group: Group of instructors
|
||||||
|
add_instructeur:
|
||||||
|
wrong_address:
|
||||||
|
one: "%{value} is not a valid email address"
|
||||||
|
other: "%{value} are not valid email addresses"
|
||||||
|
assignment:
|
||||||
|
one: "The instructor %{value} was assigned to the group « %{groupe} »."
|
||||||
|
other: "The instructors %{value} were assigned to the group « %{groupe} »."
|
||||||
|
reaffecter_dossiers:
|
||||||
|
existing_groupe:
|
||||||
|
one: "%{count} group exist"
|
||||||
|
other: "%{count} groups exist"
|
||||||
|
instructeurs:
|
||||||
|
assigned_instructeur:
|
||||||
|
one: "%{count} instructor is assigned"
|
||||||
|
other: "%{count} instructors are assigned"
|
||||||
|
edit:
|
||||||
|
routing:
|
||||||
|
title: Routing label
|
||||||
|
notice: This text will appear on the user form as the label of a list
|
||||||
|
group_management:
|
||||||
|
title: Group management
|
||||||
|
delete: delete the group
|
||||||
|
delete_confirmation: Are you sure you want to delete the group "%{group_name}"
|
||||||
|
move_folders: move folders
|
||||||
|
move_folders_confirmation: Reassign folders to another group so you can delete it
|
||||||
|
add_a_group:
|
||||||
|
title: Add a group
|
||||||
|
notice: This group will be a choice from the list "%{routing_criteria_name}"
|
||||||
|
placeholder: ex. City of Bordeaux
|
||||||
|
csv_import:
|
||||||
|
title: CSV Import
|
||||||
|
notice_1: The csv file must have 2 columns (Group, Email) and be separated by commas. The import does not overwrite existing groups and instructors.
|
||||||
|
notice_2: The size of the file must be less than %{csv_max_size}.
|
||||||
|
download_exemple: Download sample CSV file
|
||||||
|
import_file: Import file
|
||||||
|
import_file_procedure_not_published: The import of instructors by CSV file is available once the process has been published
|
||||||
|
view: view
|
||||||
|
button:
|
||||||
|
add_group: Add group
|
||||||
|
rename: Rename
|
||||||
|
existing_groupe:
|
||||||
|
one: "%{count} group exist"
|
||||||
|
other: "%{count} groups exist"
|
||||||
|
routing:
|
||||||
|
title: Routing
|
||||||
|
notice_html: |
|
||||||
|
Routing is a feature for procedures requiring the sharing of instructions between different groups according to a specific criterion (territory, theme or other).
|
||||||
|
<br><br>
|
||||||
|
This feature makes it possible to route the files to each group, and to no longer need to filter its files among a large quantity of requests. It is therefore particularly suitable for national approaches instructed locally.
|
||||||
|
<br><br>
|
||||||
|
Instructors only see the files that concern them, and therefore do not have access to data outside their scope.
|
||||||
|
self_managment_notice_html: |
|
||||||
|
Instructor Self-Management allows instructors to self-manage the list of Gait Instructors.
|
||||||
|
button:
|
||||||
|
routing_enable: Enable routing
|
||||||
|
routing_disable: Disable routing
|
|
@ -10,6 +10,8 @@ fr:
|
||||||
other: "Les experts %{value} ont été affectés à la démarche n° %{procedure}"
|
other: "Les experts %{value} ont été affectés à la démarche n° %{procedure}"
|
||||||
groupe_instructeurs:
|
groupe_instructeurs:
|
||||||
index:
|
index:
|
||||||
|
procedures: Démarches
|
||||||
|
instructors_group: Groupe d'instructeurs
|
||||||
existing_groupe:
|
existing_groupe:
|
||||||
one: "%{count} groupe existe"
|
one: "%{count} groupe existe"
|
||||||
other: "%{count} groupes existent"
|
other: "%{count} groupes existent"
|
||||||
|
@ -29,10 +31,35 @@ fr:
|
||||||
one: "%{count} instructeur est affecté"
|
one: "%{count} instructeur est affecté"
|
||||||
other: "%{count} instructeurs sont affectés"
|
other: "%{count} instructeurs sont affectés"
|
||||||
edit:
|
edit:
|
||||||
|
routing:
|
||||||
|
title: Libellé du routage
|
||||||
|
notice: Ce texte apparaitra sur le formulaire usager comme le libellé d’une liste
|
||||||
|
group_management:
|
||||||
|
title: Gestion des Groupes
|
||||||
|
delete: supprimer le groupe
|
||||||
|
delete_confirmation: Êtes-vous sûr de vouloir supprimer le groupe "%{group_name}"
|
||||||
|
move_folders: déplacer les dossiers
|
||||||
|
move_folders_confirmation: Réaffecter les dossiers à un autre groupe afin de pouvoir le supprimer
|
||||||
|
add_a_group:
|
||||||
|
title: Ajouter un groupe
|
||||||
|
notice: Ce groupe sera un choix de la liste "%{routing_criteria_name}"
|
||||||
|
placeholder: ex. Ville de Bordeaux
|
||||||
|
csv_import:
|
||||||
|
title: Importer par CSV
|
||||||
|
notice_1: Le fichier csv doit comporter 2 colonnes (Groupe, Email) et être séparé par des virgules. L'import n'écrase pas les groupes et les instructeurs existants.
|
||||||
|
notice_2: Le poids du fichier doit être inférieur à %{csv_max_size}
|
||||||
|
download_exemple: Télécharger l'exemple de fichier CSV
|
||||||
|
import_file: Importer le fichier
|
||||||
|
import_file_procedure_not_published: L’import d’instructeurs par fichier CSV est disponible une fois la démarche publiée
|
||||||
|
view: voir
|
||||||
|
button:
|
||||||
|
add_group: Ajouter le groupe
|
||||||
|
rename: Renommer
|
||||||
existing_groupe:
|
existing_groupe:
|
||||||
one: "%{count} groupe existe"
|
one: "%{count} groupe existe"
|
||||||
other: "%{count} groupes existent"
|
other: "%{count} groupes existent"
|
||||||
routing:
|
routing:
|
||||||
|
title: Routage
|
||||||
notice_html: |
|
notice_html: |
|
||||||
Le routage est une fonctionnalité pour les démarches nécessitant le partage de l’instruction entre différents groupes en fonction d’un critère précis (territoire, thématique ou autre).
|
Le routage est une fonctionnalité pour les démarches nécessitant le partage de l’instruction entre différents groupes en fonction d’un critère précis (territoire, thématique ou autre).
|
||||||
<br><br>
|
<br><br>
|
||||||
|
@ -45,3 +72,4 @@ fr:
|
||||||
routing_enable: Activer le routage
|
routing_enable: Activer le routage
|
||||||
routing_disable: Désactiver le routage
|
routing_disable: Désactiver le routage
|
||||||
self_managment_toggle: Activer l’autogestion des instructeurs
|
self_managment_toggle: Activer l’autogestion des instructeurs
|
||||||
|
add_group: Ajouter le groupe
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
en:
|
||||||
|
instructeurs:
|
||||||
|
procedures:
|
||||||
|
email_notifications:
|
||||||
|
utils:
|
||||||
|
positive: "Yes"
|
||||||
|
negative: "No"
|
||||||
|
title: Email notifications
|
||||||
|
subtitle: On this page, configure the notifications you wish to receive by email for this procedure.
|
||||||
|
for_each_file_submitted:
|
||||||
|
title: Receive a notification for each file submitted
|
||||||
|
notice_1: This email notifies you that a new file has been submitted.
|
||||||
|
notice_2: It is sent each time a user submits a file.
|
||||||
|
for_each_message_submitted:
|
||||||
|
title: Receive a notification for each message submitted
|
||||||
|
notice_1: This email notifies you that a new message has been submitted on your following files.
|
||||||
|
notice_2: It is sent each time a user submits a message.
|
||||||
|
daily_notifications:
|
||||||
|
title: Receive a daily notification
|
||||||
|
notice_1: This email notifies you of the filing of new files on this approach, or of changes to your monitored files.
|
||||||
|
notice_2: It is sent once a day, Monday to Saturday, around 10 a.m.
|
||||||
|
hebdo_recap:
|
||||||
|
title: Receive a weekly recap
|
||||||
|
notice_1: This email summarizes the activity of the week on all of your procedures.
|
||||||
|
notice_2: It is sent weekly on Monday morning.
|
||||||
|
buttons:
|
||||||
|
back_to_procedure: Back to procedure
|
||||||
|
save: Save
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
fr:
|
||||||
|
instructeurs:
|
||||||
|
procedures:
|
||||||
|
email_notifications:
|
||||||
|
utils:
|
||||||
|
positive: Oui
|
||||||
|
negative: Non
|
||||||
|
title: Notifications par email
|
||||||
|
subtitle: Configurez sur cette page les notifications que vous souhaitez recevoir par email pour cette démarche.
|
||||||
|
for_each_file_submitted:
|
||||||
|
title: Recevoir une notification à chaque dossier déposé
|
||||||
|
notice_1: Cet email vous signale le dépôt d’un nouveau dossier.
|
||||||
|
notice_2: Il est envoyé à chaque fois qu'un usager dépose un dossier.
|
||||||
|
for_each_message_submitted:
|
||||||
|
title: Recevoir une notification à chaque message déposé
|
||||||
|
notice_1: Cet email vous signale le dépôt d’un nouveau message sur vos dossiers suivis.
|
||||||
|
notice_2: Il est envoyé à chaque fois qu'un usager dépose un message.
|
||||||
|
daily_notifications:
|
||||||
|
title: Recevoir une notification quotidienne
|
||||||
|
notice_1: Cet email vous signale le dépôt de nouveaux dossiers sur cette démarche, ou des changements sur vos dossiers suivis.
|
||||||
|
notice_2: Il est envoyé une fois par jour, du lundi au samedi, vers 10 h du matin.
|
||||||
|
hebdo_recap:
|
||||||
|
title: Recevoir un récapitulatif hebdomadaire
|
||||||
|
notice_1: Cet email récapitule l’activité de la semaine sur l’ensemble de vos démarches.
|
||||||
|
notice_2: Il est envoyé chaque semaine le lundi matin.
|
||||||
|
buttons:
|
||||||
|
back_to_procedure: Revenir à la procédure
|
||||||
|
save: Enregistrer
|
|
@ -35,3 +35,8 @@ en:
|
||||||
export_monthly_pending_html: An export of the last 30 days in the format %{export_format} is being generated<br>(asked %{export_time} ago)
|
export_monthly_pending_html: An export of the last 30 days in the format %{export_format} is being generated<br>(asked %{export_time} ago)
|
||||||
download_archive: Download a .zip archive of all files and their attachments
|
download_archive: Download a .zip archive of all files and their attachments
|
||||||
download: Download all files
|
download: Download all files
|
||||||
|
email_usagers:
|
||||||
|
contact_users: Contact users (draft)
|
||||||
|
notice: "You will send a message to %{dossiers_count} whose files are in draft, in the instructor groups : %{groupe_instructeurs}."
|
||||||
|
stats:
|
||||||
|
title: Statistics
|
||||||
|
|
|
@ -35,3 +35,8 @@ fr:
|
||||||
export_monthly_pending_html: Un export des 30 derniers jours au format %{export_format} est en train d’être généré<br>(demandé il y a %{export_time})
|
export_monthly_pending_html: Un export des 30 derniers jours au format %{export_format} est en train d’être généré<br>(demandé il y a %{export_time})
|
||||||
download_archive: Télécharger une archive au format .zip de tous les dossiers et leurs pièces jointes
|
download_archive: Télécharger une archive au format .zip de tous les dossiers et leurs pièces jointes
|
||||||
download: Télécharger tous les dossiers
|
download: Télécharger tous les dossiers
|
||||||
|
email_usagers:
|
||||||
|
contact_users: Contacter les usagers (brouillon)
|
||||||
|
notice: "Vous allez envoyer un message à %{dossiers_count} dont les dossiers sont en brouillon, dans les groupes instructeurs : %{groupe_instructeurs}."
|
||||||
|
stats:
|
||||||
|
title: Statistiques
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
en:
|
en:
|
||||||
layouts:
|
layouts:
|
||||||
|
header:
|
||||||
|
files: Files
|
||||||
go_superadmin: "Switch to super-admin"
|
go_superadmin: "Switch to super-admin"
|
||||||
go_user: "Switch to user"
|
go_user: "Switch to user"
|
||||||
go_instructor: "Switch to instructor"
|
go_instructor: "Switch to instructor"
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
fr:
|
fr:
|
||||||
layouts:
|
layouts:
|
||||||
|
header:
|
||||||
|
files: Dossiers
|
||||||
go_superadmin: "Passer en super-admin"
|
go_superadmin: "Passer en super-admin"
|
||||||
go_user: "Passer en usager"
|
go_user: "Passer en usager"
|
||||||
go_instructor: "Passer en instructeur"
|
go_instructor: "Passer en instructeur"
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"@babel/preset-typescript": "^7.16.7",
|
"@babel/preset-typescript": "^7.16.7",
|
||||||
"@headlessui/react": "^1.5.0",
|
"@headlessui/react": "^1.5.0",
|
||||||
"@heroicons/react": "^1.0.6",
|
"@heroicons/react": "^1.0.6",
|
||||||
|
"@hotwired/stimulus": "^3.0.1",
|
||||||
"@mapbox/mapbox-gl-draw": "^1.3.0",
|
"@mapbox/mapbox-gl-draw": "^1.3.0",
|
||||||
"@popperjs/core": "^2.11.4",
|
"@popperjs/core": "^2.11.4",
|
||||||
"@rails/actiontext": "^6.1.4-1",
|
"@rails/actiontext": "^6.1.4-1",
|
||||||
|
|
|
@ -87,7 +87,7 @@ describe 'shared/dossiers/edit.html.haml', type: :view do
|
||||||
let(:champ_value) { ['banana', 'grapefruit'].to_json }
|
let(:champ_value) { ['banana', 'grapefruit'].to_json }
|
||||||
|
|
||||||
it 'renders the list as a multiple-selection dropdown' do
|
it 'renders the list as a multiple-selection dropdown' do
|
||||||
expect(subject).to have_selector('[data-react-class="ComboMultipleDropdownList"]')
|
expect(subject).to have_selector('[data-react-component-value="ComboMultipleDropdownList"]')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1269,6 +1269,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-1.0.6.tgz#35dd26987228b39ef2316db3b1245c42eb19e324"
|
resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-1.0.6.tgz#35dd26987228b39ef2316db3b1245c42eb19e324"
|
||||||
integrity sha512-JJCXydOFWMDpCP4q13iEplA503MQO3xLoZiKum+955ZCtHINWnx26CUxVxxFQu/uLb4LW3ge15ZpzIkXKkJ8oQ==
|
integrity sha512-JJCXydOFWMDpCP4q13iEplA503MQO3xLoZiKum+955ZCtHINWnx26CUxVxxFQu/uLb4LW3ge15ZpzIkXKkJ8oQ==
|
||||||
|
|
||||||
|
"@hotwired/stimulus@^3.0.1":
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@hotwired/stimulus/-/stimulus-3.0.1.tgz#141f15645acaa3b133b7c247cad58ae252ffae85"
|
||||||
|
integrity sha512-oHsJhgY2cip+K2ED7vKUNd2P+BEswVhrCYcJ802DSsblJFv7mPFVk3cQKvm2vHgHeDVdnj7oOKrBbzp1u8D+KA==
|
||||||
|
|
||||||
"@humanwhocodes/config-array@^0.5.0":
|
"@humanwhocodes/config-array@^0.5.0":
|
||||||
version "0.5.0"
|
version "0.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9"
|
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9"
|
||||||
|
|
Loading…
Reference in a new issue