Merge branch 'main' into bug-input-pj-apres-suppression
This commit is contained in:
commit
ceedbe9c45
37 changed files with 221 additions and 368 deletions
|
@ -10,7 +10,7 @@ class Attachment::EditComponent < ApplicationComponent
|
|||
|
||||
EXTENSIONS_ORDER = ['jpeg', 'png', 'pdf', 'zip'].freeze
|
||||
|
||||
def initialize(champ: nil, auto_attach_url: nil, attached_file:, direct_upload: true, index: 0, as_multiple: false, view_as: :link, user_can_destroy: true, user_can_replace: false, attachments: [], **kwargs)
|
||||
def initialize(champ: nil, auto_attach_url: nil, attached_file:, direct_upload: true, index: 0, as_multiple: false, view_as: :link, user_can_destroy: true, user_can_replace: false, attachments: [], max: nil, **kwargs)
|
||||
@champ = champ
|
||||
@attached_file = attached_file
|
||||
@direct_upload = direct_upload
|
||||
|
@ -24,6 +24,7 @@ class Attachment::EditComponent < ApplicationComponent
|
|||
@attachments = attachments.presence || (kwargs.key?(:attachment) ? [kwargs.delete(:attachment)] : [])
|
||||
@attachments << attached_file.attachment if attached_file.respond_to?(:attachment) && @attachments.empty?
|
||||
@attachments.compact!
|
||||
@max = max
|
||||
|
||||
# Utilisation du premier attachement comme référence pour la rétrocompatibilité
|
||||
@attachment = @attachments.first
|
||||
|
@ -54,7 +55,7 @@ class Attachment::EditComponent < ApplicationComponent
|
|||
end
|
||||
|
||||
def destroy_attachment_path
|
||||
attachment_path(champ_id: champ&.public_id)
|
||||
attachment_path(dossier_id: champ&.dossier_id, stable_id: champ&.stable_id, row_id: champ&.row_id)
|
||||
end
|
||||
|
||||
def attachment_input_class
|
||||
|
@ -63,6 +64,7 @@ class Attachment::EditComponent < ApplicationComponent
|
|||
|
||||
def file_field_options
|
||||
track_issue_with_missing_validators if missing_validators?
|
||||
|
||||
options = {
|
||||
class: class_names("fr-upload attachment-input": true, "#{attachment_input_class}": true, "hidden": persisted?),
|
||||
direct_upload: @direct_upload,
|
||||
|
@ -76,6 +78,7 @@ class Attachment::EditComponent < ApplicationComponent
|
|||
|
||||
options.merge!(has_content_type_validator? ? { accept: accept_content_type } : {})
|
||||
options[:multiple] = true if as_multiple?
|
||||
options[:disabled] = true if @max && @index >= @max
|
||||
|
||||
options
|
||||
end
|
||||
|
|
|
@ -30,10 +30,6 @@ class Attachment::MultipleComponent < ApplicationComponent
|
|||
@attachments.each_with_index(&block)
|
||||
end
|
||||
|
||||
def can_attach_next?
|
||||
@attachments.count < @max
|
||||
end
|
||||
|
||||
def empty_component_id
|
||||
champ.present? ? "attachment-multiple-empty-#{champ.public_id}" : "attachment-multiple-empty-generic"
|
||||
end
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
%li{ id: dom_id(attachment) }
|
||||
= render Attachment::EditComponent.new(champ:, attached_file:, attachment:, index:, view_as:, user_can_destroy:, form_object_name:)
|
||||
|
||||
%div{ id: empty_component_id, class: class_names("hidden": !can_attach_next?), data: { turbo_force: :server } }
|
||||
= render Attachment::EditComponent.new(champ:, as_multiple: champ.nil?, attached_file:, attachment: nil, index: attachments_count, user_can_destroy:, form_object_name:)
|
||||
%div{ id: empty_component_id, data: { turbo_force: :server } }
|
||||
= render Attachment::EditComponent.new(champ:, as_multiple: champ.nil?, attached_file:, attachment: nil, index: attachments_count, user_can_destroy:, form_object_name:, max: @max)
|
||||
|
||||
// single poll and refresh message for all attachments
|
||||
= render Attachment::PendingPollComponent.new(attachments: attachments, poll_url:, context: poll_context)
|
||||
|
|
|
@ -2,6 +2,17 @@
|
|||
class Dsfr::AlertComponent < ApplicationComponent
|
||||
renders_one :body
|
||||
|
||||
attr_reader :state, :title, :size, :block, :extra_class_names, :heading_level
|
||||
|
||||
def initialize(state:, title: '', size: '', extra_class_names: nil, heading_level: 'h3')
|
||||
@state = state
|
||||
@title = title
|
||||
@size = size
|
||||
@block = block
|
||||
@extra_class_names = extra_class_names
|
||||
@heading_level = heading_level
|
||||
end
|
||||
|
||||
def prefix_for_state
|
||||
case state
|
||||
when :error then "Erreur : "
|
||||
|
@ -19,19 +30,4 @@ class Dsfr::AlertComponent < ApplicationComponent
|
|||
extra_class_names => true
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def initialize(state:, title: '', size: '', extra_class_names: nil, heading_level: 'h3')
|
||||
@state = state
|
||||
@title = title
|
||||
@size = size
|
||||
@block = block
|
||||
@extra_class_names = extra_class_names
|
||||
@heading_level = heading_level
|
||||
end
|
||||
|
||||
attr_reader :state, :title, :size, :block, :extra_class_names, :heading_level
|
||||
|
||||
private
|
||||
end
|
||||
|
|
21
app/components/procedure_draft_warning_component.rb
Normal file
21
app/components/procedure_draft_warning_component.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ProcedureDraftWarningComponent < ApplicationComponent
|
||||
attr_reader :revision
|
||||
attr_reader :current_administrateur
|
||||
attr_reader :extra_class_names
|
||||
|
||||
def initialize(revision:, current_administrateur:, extra_class_names: nil)
|
||||
@revision = revision
|
||||
@current_administrateur = current_administrateur
|
||||
@extra_class_names = extra_class_names
|
||||
end
|
||||
|
||||
def render?
|
||||
revision.draft?
|
||||
end
|
||||
|
||||
def admin?
|
||||
current_administrateur.present? && revision.procedure.administrateurs.include?(current_administrateur)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
en:
|
||||
title: Procedure in testing
|
||||
intro_procedure_brouillon_html: This procedure is currently in <b>testing</b>
|
||||
intro_revision_draft_html: This page allows you to <b>test</b> a new version of the procedure
|
||||
body_general_html: |
|
||||
and this page is reserved for the administration in charge of its deployment.
|
||||
If you start or submit a file, it may be <b>deleted at any time</b> without notice, even if it is accepted later.
|
||||
body_user: |
|
||||
If this link was shared with you, please contact the service in charge of this procedure
|
||||
to obtain the public link for the procedure in order to submit your application.
|
||||
body_admin_procedure_brouillon: Do not share this link with your users. When you publish the procedure, you will access the public link for the procedure to be shared.
|
||||
body_admin_revision_draft: Do not share this link with your users, but rather the public link for the procedure displayed in your administrator dashboard.
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
fr:
|
||||
title: Démarche en test
|
||||
intro_procedure_brouillon_html: Cette démarche est actuellement en <b>test</b>
|
||||
intro_revision_draft_html: Cette page permet de <b>tester</b> une nouvelle version de la démarche
|
||||
body_general_html: |
|
||||
et cette page est réservée à l’administration en charge de son déploiement.
|
||||
Si vous commencez ou déposez un dossier, il pourra être <b>supprimé à tout moment</b> et sans préavis, même après avoir été accepté.
|
||||
body_user: |
|
||||
Si ce lien vous a été communiqué, contactez le service en charge de cette démarche
|
||||
pour obtenir le lien public de la démarche afin de déposer votre dossier.
|
||||
body_admin_procedure_brouillon: Ne communiquez pas ce lien à vos usagers. Lorsque vous publierez la démarche, vous accéderez au lien public de la démarche à communiquer.
|
||||
body_admin_revision_draft: Ne communiquez pas ce lien à vos usagers, mais le lien public de la démarche affiché dans votre tableau de bord administrateur.
|
|
@ -0,0 +1,10 @@
|
|||
= render Dsfr::AlertComponent.new(state: :warning, extra_class_names:, title: t(".title")) do |c|
|
||||
- c.with_body do
|
||||
%p
|
||||
= revision.procedure.brouillon? ? t(".intro_procedure_brouillon_html") : t(".intro_revision_draft_html")
|
||||
= t(".body_general_html")
|
||||
|
||||
- if admin?
|
||||
%p= revision.procedure.brouillon? ? t(".body_admin_procedure_brouillon") : t(".body_admin_revision_draft")
|
||||
- else
|
||||
%p= t(".body_user")
|
|
@ -12,8 +12,8 @@ class AgentConnect::AgentController < ApplicationController
|
|||
def login
|
||||
uri, state, nonce = AgentConnectService.authorization_uri
|
||||
|
||||
cookies.encrypted[STATE_COOKIE_NAME] = state
|
||||
cookies.encrypted[NONCE_COOKIE_NAME] = nonce
|
||||
cookies.encrypted[STATE_COOKIE_NAME] = { value: state, secure: Rails.env.production?, httponly: true }
|
||||
cookies.encrypted[NONCE_COOKIE_NAME] = { value: nonce, secure: Rails.env.production?, httponly: true }
|
||||
|
||||
redirect_to uri, allow_other_host: true
|
||||
end
|
||||
|
|
|
@ -117,7 +117,7 @@ class ApplicationController < ActionController::Base
|
|||
|
||||
def set_locale(locale)
|
||||
if locale && locale.to_sym.in?(I18n.available_locales)
|
||||
cookies[:locale] = locale
|
||||
cookies[:locale] = { value: locale, secure: Rails.env.production?, httponly: true }
|
||||
if user_signed_in?
|
||||
current_user.update(locale: locale)
|
||||
end
|
||||
|
|
|
@ -24,7 +24,8 @@ module ApplicationController::LongLivedAuthenticityToken
|
|||
cookies.signed[COOKIE_NAME] = {
|
||||
value: csrf_token,
|
||||
expires: 1.year.from_now,
|
||||
httponly: true
|
||||
httponly: true,
|
||||
secure: Rails.env.production?
|
||||
}
|
||||
session[:_csrf_token] = csrf_token
|
||||
|
||||
|
|
|
@ -21,11 +21,18 @@ class AttachmentsController < ApplicationController
|
|||
@attachment.purge_later
|
||||
flash.notice = 'La pièce jointe a bien été supprimée.'
|
||||
|
||||
@champ_id = params[:champ_id]
|
||||
@champ = find_champ if params[:dossier_id]
|
||||
|
||||
respond_to do |format|
|
||||
format.turbo_stream
|
||||
format.html { redirect_back(fallback_location: root_url) }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_champ
|
||||
dossier = policy_scope(Dossier).includes(:champs).find(params[:dossier_id])
|
||||
dossier.champs.find_by(stable_id: params[:stable_id], row_id: params[:row_id])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -247,7 +247,9 @@ module Instructeurs
|
|||
@export_templates = current_instructeur.export_templates_for(@procedure).includes(:groupe_instructeur)
|
||||
cookies.encrypted[cookies_export_key] = {
|
||||
value: DateTime.current,
|
||||
expires: Export::MAX_DUREE_GENERATION + Export::MAX_DUREE_CONSERVATION_EXPORT
|
||||
expires: Export::MAX_DUREE_GENERATION + Export::MAX_DUREE_CONSERVATION_EXPORT,
|
||||
httponly: true,
|
||||
secure: Rails.env.production?
|
||||
}
|
||||
|
||||
respond_to do |format|
|
||||
|
|
|
@ -52,11 +52,14 @@ export function useSingleList({
|
|||
}: {
|
||||
defaultItems?: Item[];
|
||||
defaultSelectedKey?: string | null;
|
||||
emptyFilterKey?: string;
|
||||
emptyFilterKey?: string | null;
|
||||
onChange?: (item: Item | null) => void;
|
||||
}) {
|
||||
const [selectedKey, setSelectedKey] = useState(defaultSelectedKey);
|
||||
const items = useMemo(() => defaultItems || [], [defaultItems]);
|
||||
const items = useMemo(
|
||||
() => (defaultItems ? distinctBy(defaultItems, 'value') : []),
|
||||
[defaultItems]
|
||||
);
|
||||
const selectedItem = useMemo(
|
||||
() => items.find((item) => item.value == selectedKey) ?? null,
|
||||
[items, selectedKey]
|
||||
|
@ -82,8 +85,8 @@ export function useSingleList({
|
|||
const initialSelectedKeyRef = useRef(defaultSelectedKey);
|
||||
|
||||
const setSelection = useEvent((key?: string | null) => {
|
||||
const inputValue = defaultSelectedKey
|
||||
? items.find((item) => item.value == defaultSelectedKey)?.label
|
||||
const inputValue = key
|
||||
? items.find((item) => item.value == key)?.label
|
||||
: '';
|
||||
setSelectedKey(key);
|
||||
setInputValue(inputValue ?? '');
|
||||
|
@ -157,7 +160,10 @@ export function useMultiList({
|
|||
() => new Set(defaultSelectedKeys ?? [])
|
||||
);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const items = useMemo(() => defaultItems || [], [defaultItems]);
|
||||
const items = useMemo(
|
||||
() => (defaultItems ? distinctBy(defaultItems, 'value') : []),
|
||||
[defaultItems]
|
||||
);
|
||||
const itemsIndex = useMemo(() => {
|
||||
const index = new Map<string, Item>();
|
||||
for (const item of items) {
|
||||
|
@ -473,3 +479,8 @@ export function useOnFormReset(onReset?: () => void) {
|
|||
|
||||
return ref;
|
||||
}
|
||||
|
||||
function distinctBy<T>(array: T[], key: keyof T): T[] {
|
||||
const keys = array.map((item) => item[key]);
|
||||
return array.filter((item, index) => keys.indexOf(item[key]) == index);
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ export const SingleComboBoxProps = s.assign(
|
|||
s.partial(
|
||||
s.object({
|
||||
selectedKey: s.nullable(s.string()),
|
||||
emptyFilterKey: s.string()
|
||||
emptyFilterKey: s.nullable(s.string())
|
||||
})
|
||||
)
|
||||
);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import format from 'date-fns/format';
|
||||
import { format } from 'date-fns/format';
|
||||
|
||||
import { ApplicationController } from './application_controller';
|
||||
|
||||
|
|
|
@ -1,156 +0,0 @@
|
|||
import type { AxeResults, NodeResult, RelatedNode } from 'axe-core';
|
||||
import axe from 'axe-core';
|
||||
|
||||
domReady().then(() => {
|
||||
axe.run(document.body, { reporter: 'v2' }).then((results) => {
|
||||
logToConsole(results);
|
||||
});
|
||||
});
|
||||
|
||||
// contrasted against Chrome default color of #ffffff
|
||||
const lightTheme = {
|
||||
serious: '#d93251',
|
||||
minor: '#d24700',
|
||||
text: 'black'
|
||||
};
|
||||
|
||||
// contrasted against Safari dark mode color of #535353
|
||||
const darkTheme = {
|
||||
serious: '#ffb3b3',
|
||||
minor: '#ffd500',
|
||||
text: 'white'
|
||||
};
|
||||
|
||||
const theme =
|
||||
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
? darkTheme
|
||||
: lightTheme;
|
||||
|
||||
const boldCourier = 'font-weight:bold;font-family:Courier;';
|
||||
const critical = `color:${theme.serious};font-weight:bold;`;
|
||||
const serious = `color:${theme.serious};font-weight:normal;`;
|
||||
const moderate = `color:${theme.minor};font-weight:bold;`;
|
||||
const minor = `color:${theme.minor};font-weight:normal;`;
|
||||
const defaultReset = `font-color:${theme.text};font-weight:normal;`;
|
||||
|
||||
function logToConsole(results: AxeResults): void {
|
||||
console.group('%cNew axe issues', serious);
|
||||
results.violations.forEach((result) => {
|
||||
let fmt: string;
|
||||
switch (result.impact) {
|
||||
case 'critical':
|
||||
fmt = critical;
|
||||
break;
|
||||
case 'serious':
|
||||
fmt = serious;
|
||||
break;
|
||||
case 'moderate':
|
||||
fmt = moderate;
|
||||
break;
|
||||
case 'minor':
|
||||
fmt = minor;
|
||||
break;
|
||||
default:
|
||||
fmt = minor;
|
||||
break;
|
||||
}
|
||||
console.groupCollapsed(
|
||||
'%c%s: %c%s %s',
|
||||
fmt,
|
||||
result.impact,
|
||||
defaultReset,
|
||||
result.help,
|
||||
result.helpUrl
|
||||
);
|
||||
result.nodes.forEach((node) => {
|
||||
failureSummary(node, 'any');
|
||||
failureSummary(node, 'none');
|
||||
});
|
||||
console.groupEnd();
|
||||
});
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
function failureSummary(node: NodeResult, key: AxeCoreNodeResultKey): void {
|
||||
if (node[key].length > 0) {
|
||||
logElement(node, console.groupCollapsed);
|
||||
logHtml(node);
|
||||
logFailureMessage(node, key);
|
||||
|
||||
let relatedNodes: RelatedNode[] = [];
|
||||
node[key].forEach((check) => {
|
||||
relatedNodes = relatedNodes.concat(check.relatedNodes ?? []);
|
||||
});
|
||||
|
||||
if (relatedNodes.length > 0) {
|
||||
console.groupCollapsed('Related nodes');
|
||||
relatedNodes.forEach((relatedNode) => {
|
||||
logElement(relatedNode, console.log);
|
||||
logHtml(relatedNode);
|
||||
});
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
console.groupEnd();
|
||||
}
|
||||
}
|
||||
|
||||
function logFailureMessage(node: NodeResult, key: AxeCoreNodeResultKey): void {
|
||||
// this exists on axe but we don't export it as part of the typescript
|
||||
// namespace, so just let me use it as I need
|
||||
const message: string = (
|
||||
axe as unknown as AxeWithAudit
|
||||
)._audit.data.failureSummaries[key].failureMessage(
|
||||
node[key].map((check) => check.message || '')
|
||||
);
|
||||
|
||||
console.error(message);
|
||||
}
|
||||
|
||||
function logElement(
|
||||
node: NodeResult | RelatedNode,
|
||||
logFn: (...args: unknown[]) => void
|
||||
): void {
|
||||
const el = document.querySelector(node.target.toString());
|
||||
if (!el) {
|
||||
logFn('Selector: %c%s', boldCourier, node.target.toString());
|
||||
} else {
|
||||
logFn('Element: %o', el);
|
||||
}
|
||||
}
|
||||
|
||||
function logHtml(node: NodeResult | RelatedNode): void {
|
||||
console.log('HTML: %c%s', boldCourier, node.html);
|
||||
}
|
||||
|
||||
type AxeCoreNodeResultKey = 'any' | 'all' | 'none';
|
||||
|
||||
interface AxeWithAudit {
|
||||
_audit: {
|
||||
data: {
|
||||
failureSummaries: {
|
||||
any: {
|
||||
failureMessage: (args: string[]) => string;
|
||||
};
|
||||
all: {
|
||||
failureMessage: (args: string[]) => string;
|
||||
};
|
||||
none: {
|
||||
failureMessage: (args: string[]) => string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function domReady() {
|
||||
return new Promise<void>((resolve) => {
|
||||
if (document.readyState == 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => resolve(), {
|
||||
once: true
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
@preserve dataset polyfill for IE < 11. See https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset and http://caniuse.com/#search=dataset
|
||||
|
||||
@author ShirtlessKirk copyright 2015
|
||||
@license WTFPL (http://www.wtfpl.net/txt/copying)
|
||||
*/
|
||||
|
||||
const dash = /-([a-z])/gi;
|
||||
const dataRegEx = /^data-(.+)/;
|
||||
const hasEventListener = !!document.addEventListener;
|
||||
const test = document.createElement('_');
|
||||
const DOMAttrModified = 'DOMAttrModified';
|
||||
|
||||
let mutationSupport = false;
|
||||
|
||||
function clearDataset(event) {
|
||||
delete event.target._datasetCache;
|
||||
}
|
||||
|
||||
function toCamelCase(string) {
|
||||
return string.replace(dash, function (_, letter) {
|
||||
return letter.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
function getDataset() {
|
||||
const dataset = {};
|
||||
|
||||
for (let attribute of this.attributes) {
|
||||
let match = attribute.name.match(dataRegEx);
|
||||
if (match) {
|
||||
dataset[toCamelCase(match[1])] = attribute.value;
|
||||
}
|
||||
}
|
||||
|
||||
return dataset;
|
||||
}
|
||||
|
||||
function mutation() {
|
||||
if (hasEventListener) {
|
||||
test.removeEventListener(DOMAttrModified, mutation, false);
|
||||
} else {
|
||||
test.detachEvent(`on${DOMAttrModified}`, mutation);
|
||||
}
|
||||
|
||||
mutationSupport = true;
|
||||
}
|
||||
|
||||
if (!test.dataset) {
|
||||
if (hasEventListener) {
|
||||
test.addEventListener(DOMAttrModified, mutation, false);
|
||||
} else {
|
||||
test.attachEvent(`on${DOMAttrModified}`, mutation);
|
||||
}
|
||||
|
||||
// trigger event (if supported)
|
||||
test.setAttribute('foo', 'bar');
|
||||
|
||||
Object.defineProperty(Element.prototype, 'dataset', {
|
||||
get: mutationSupport
|
||||
? function get() {
|
||||
if (!this._datasetCache) {
|
||||
this._datasetCache = getDataset.call(this);
|
||||
}
|
||||
|
||||
return this._datasetCache;
|
||||
}
|
||||
: getDataset
|
||||
});
|
||||
|
||||
if (mutationSupport && hasEventListener) {
|
||||
// < IE9 supports neither
|
||||
document.addEventListener(DOMAttrModified, clearDataset, false);
|
||||
}
|
||||
}
|
|
@ -107,11 +107,6 @@ class Champ < ApplicationRecord
|
|||
[to_s]
|
||||
end
|
||||
|
||||
def valid_value
|
||||
return unless valid_champ_value?
|
||||
value
|
||||
end
|
||||
|
||||
def to_s
|
||||
TypeDeChamp.champ_value(type_champ, self)
|
||||
end
|
||||
|
|
|
@ -50,7 +50,7 @@ class Champs::COJOChamp < Champ
|
|||
|
||||
def update_external_id
|
||||
if accreditation_number_changed? || accreditation_birthdate_changed?
|
||||
if accreditation_number.present? && accreditation_birthdate.present? && /\A\d+\z/.match?(accreditation_number)
|
||||
if accreditation_number.present? && accreditation_birthdate.present? && /\A[\d-]+\z/.match?(accreditation_number)
|
||||
self.external_id = { accreditation_number:, accreditation_birthdate: }.to_json
|
||||
else
|
||||
self.external_id = nil
|
||||
|
|
|
@ -8,7 +8,8 @@ module TrustedDeviceConcern
|
|||
cookies.encrypted[TRUSTED_DEVICE_COOKIE_NAME] = {
|
||||
value: JSON.generate({ created_at: start_at }),
|
||||
expires: start_at + TRUSTED_DEVICE_PERIOD,
|
||||
httponly: true
|
||||
httponly: true,
|
||||
secure: Rails.env.production?
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ class TypesDeChamp::DecimalNumberTypeDeChamp < TypesDeChamp::TypeDeChampBase
|
|||
private
|
||||
|
||||
def champ_formatted_value(champ)
|
||||
champ.valid_value&.to_f
|
||||
champ.value&.to_f
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,7 +20,7 @@ class TypesDeChamp::IntegerNumberTypeDeChamp < TypesDeChamp::TypeDeChampBase
|
|||
private
|
||||
|
||||
def champ_formatted_value(champ)
|
||||
champ.valid_value&.to_i
|
||||
champ.value&.to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -66,12 +66,12 @@ class TypesDeChamp::TypeDeChampBase
|
|||
when 2
|
||||
champ_value(champ)
|
||||
else
|
||||
champ.valid_value.presence || champ_default_api_value(version)
|
||||
champ.value.presence || champ_default_api_value(version)
|
||||
end
|
||||
end
|
||||
|
||||
def champ_value_for_export(champ, path = :value)
|
||||
path == :value ? champ.valid_value.presence : champ_default_export_value(path)
|
||||
path == :value ? champ.value.presence : champ_default_export_value(path)
|
||||
end
|
||||
|
||||
def champ_value_for_tag(champ, path = :value)
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
= turbo_stream.remove dom_id(@attachment, :persisted_row)
|
||||
|
||||
- if @champ_id
|
||||
= turbo_stream.show "attachment-multiple-empty-#{@champ_id}"
|
||||
= turbo_stream.focus_all "#attachment-multiple-empty-#{@champ_id} input"
|
||||
|
||||
= turbo_stream.show_all ".attachment-input-#{@attachment.id}"
|
||||
|
||||
- if @champ
|
||||
= fields_for @champ.input_name, @champ do |form|
|
||||
= turbo_stream.replace @champ.input_group_id do
|
||||
= render EditableChamp::EditableChampComponent.new champ: @champ, form: form
|
||||
= turbo_stream.focus_all "#attachment-multiple-empty-#{@champ.public_id} input"
|
||||
|
|
|
@ -13,7 +13,11 @@
|
|||
#{Current.application_name}
|
||||
%li= link_to t('views.shared.account.already_user'), commencer_sign_in_path(path: @procedure.path, prefill_token: @prefilled_dossier&.prefill_token), class: 'fr-btn fr-btn--secondary'
|
||||
|
||||
= render ProcedureDraftWarningComponent.new(revision: @revision, current_administrateur:, extra_class_names: "fr-mb-2w")
|
||||
|
||||
- else
|
||||
= render ProcedureDraftWarningComponent.new(revision: @revision, current_administrateur:, extra_class_names: "fr-mb-2w")
|
||||
|
||||
- if @prefilled_dossier
|
||||
= render Dsfr::CalloutComponent.new(title: t(".prefilled_draft"), heading_level: 'h2') do |c|
|
||||
- c.with_body do
|
||||
|
|
|
@ -2,4 +2,9 @@
|
|||
|
||||
= render partial: 'header', locals: { avis: @avis, dossier: @dossier }
|
||||
|
||||
= render partial: 'shared/dossiers/demande', locals: { dossier: @dossier, demande_seen_at: nil, profile: 'expert' }
|
||||
.fr-container
|
||||
.fr-grid-row.fr-grid-row--center
|
||||
- summary = ViewableChamp::HeaderSectionsSummaryComponent.new(dossier: @dossier, is_private: false)
|
||||
= render summary
|
||||
%div{ class: class_names("fr-col-12", "fr-col-xl-9" => summary.render?, "fr-col-xl-8" => !summary.render?) }
|
||||
= render partial: "shared/dossiers/demande", locals: { dossier: @dossier, demande_seen_at: nil, profile: 'expert' }
|
||||
|
|
|
@ -6,20 +6,15 @@
|
|||
.dossier-container.fr-mb-4w
|
||||
= render partial: 'users/dossiers/show/header', locals: { dossier: @dossier }
|
||||
|
||||
- if @dossier.en_construction?
|
||||
.fr-container
|
||||
.fr-grid-row.fr-grid-row--center
|
||||
.fr-col-xl-10
|
||||
= render Dossiers::EnConstructionNotSubmittedComponent.new(dossier: @dossier, user: current_user)
|
||||
.fr-container
|
||||
.fr-grid-row.fr-grid-row--center
|
||||
.fr-col-md-9
|
||||
- if @dossier.en_construction?
|
||||
= render Dossiers::EnConstructionNotSubmittedComponent.new(dossier: @dossier, user: current_user)
|
||||
|
||||
= render partial: 'shared/dossiers/demande', locals: { dossier: @dossier, demande_seen_at: nil, profile: 'usager' }
|
||||
|
||||
|
||||
- if !@dossier.read_only?
|
||||
.fr-container.fr-mt-2w
|
||||
.fr-grid-row.fr-grid-row--center
|
||||
.fr-col-xl-8.fr-col-offset-xl-2
|
||||
%p= link_to t('views.users.dossiers.demande.edit_dossier'), modifier_dossier_path(@dossier), class: 'fr-btn fr-btn-sm',
|
||||
title: t('views.users.dossiers.demande.edit_dossier_title')
|
||||
- if !@dossier.read_only?
|
||||
.fr-px-2w.fr-mt-2w
|
||||
%p= link_to t('views.users.dossiers.demande.edit_dossier'), modifier_dossier_path(@dossier), class: 'fr-btn fr-btn-sm',
|
||||
title: t('views.users.dossiers.demande.edit_dossier_title')
|
||||
|
|
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
@ -1,3 +1,3 @@
|
|||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
Rails.application.config.session_store :cookie_store, key: '_DS_session'
|
||||
Rails.application.config.session_store :cookie_store, key: '_DS_session', secure: Rails.env.production?, httponly: true
|
||||
|
|
|
@ -10,7 +10,7 @@ fr:
|
|||
since: "depuis le %{date}"
|
||||
closed: "Close"
|
||||
published: "Publiée"
|
||||
draft: "En test"
|
||||
draft: "En test"
|
||||
more_info_on_test: "Pour plus d’information sur la phase de test"
|
||||
go_to_FAQ: "consulter la FAQ"
|
||||
url_FAQ: "/faq#accordion-administrateur-2"
|
||||
|
|
57
package.json
57
package.json
|
@ -5,9 +5,9 @@
|
|||
"@coldwired/react": "^0.15.0",
|
||||
"@coldwired/turbo-stream": "^0.13.0",
|
||||
"@coldwired/utils": "^0.13.0",
|
||||
"@frsource/autoresize-textarea": "^2.0.75",
|
||||
"@frsource/autoresize-textarea": "^2.0.82",
|
||||
"@gouvfr/dsfr": "^1.11.2",
|
||||
"@graphiql/plugin-explorer": "^3.0.2",
|
||||
"@graphiql/plugin-explorer": "^3.1.0",
|
||||
"@graphiql/toolkit": "^0.9.1",
|
||||
"@headlessui/react": "^1.6.6",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
|
@ -15,11 +15,11 @@
|
|||
"@hotwired/turbo": "^7.3.0",
|
||||
"@mapbox/mapbox-gl-draw": "^1.3.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@rails/actiontext": "^7.1.3-2",
|
||||
"@rails/activestorage": "^7.1.3-2",
|
||||
"@rails/ujs": "^7.1.3-2",
|
||||
"@rails/actiontext": "^7.1.3-4",
|
||||
"@rails/activestorage": "^7.1.3-4",
|
||||
"@rails/ujs": "^7.1.3-4",
|
||||
"@reach/slider": "^0.17.0",
|
||||
"@sentry/browser": "8.7.0",
|
||||
"@sentry/browser": "8.15.0",
|
||||
"@tiptap/core": "^2.2.4",
|
||||
"@tiptap/extension-bold": "^2.2.4",
|
||||
"@tiptap/extension-bullet-list": "^2.2.4",
|
||||
|
@ -44,26 +44,26 @@
|
|||
"@tmcw/togeojson": "^5.6.0",
|
||||
"chartkick": "^5.0.1",
|
||||
"core-js": "^3.37.1",
|
||||
"date-fns": "^2.30.0",
|
||||
"debounce": "^1.2.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"debounce": "^2.1.0",
|
||||
"geojson": "^0.5.0",
|
||||
"graphiql": "^3.2.3",
|
||||
"graphql": "^16.8.1",
|
||||
"graphiql": "^3.3.2",
|
||||
"graphql": "^16.9.0",
|
||||
"highcharts": "^10.3.3",
|
||||
"lightgallery": "^2.7.2",
|
||||
"maplibre-gl": "^1.15.2",
|
||||
"match-sorter": "^6.3.4",
|
||||
"patch-package": "^8.0.0",
|
||||
"react": "^18.3.0",
|
||||
"react-aria-components": "^1.2.0",
|
||||
"react": "^18.3.1",
|
||||
"react-aria-components": "^1.2.1",
|
||||
"react-coordinate-input": "^1.0.0",
|
||||
"react-dom": "^18.3.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-popper": "^2.3.0",
|
||||
"react-use-event-hook": "^0.9.6",
|
||||
"spectaql": "^2.3.1",
|
||||
"stimulus-use": "^0.52.2",
|
||||
"superstruct": "^1.0.4",
|
||||
"terser": "^5.31.0",
|
||||
"superstruct": "^2.0.2",
|
||||
"terser": "^5.31.1",
|
||||
"tiny-invariant": "^1.3.3",
|
||||
"tippy.js": "^6.3.7",
|
||||
"trix": "^1.2.3",
|
||||
|
@ -73,7 +73,7 @@
|
|||
"@esbuild/darwin-arm64": "=0.19.9",
|
||||
"@esbuild/linux-x64": "=0.19.9",
|
||||
"@esbuild/win32-x64": "=0.19.9",
|
||||
"@react-aria/optimize-locales-plugin": "^1.1.0",
|
||||
"@react-aria/optimize-locales-plugin": "^1.1.1",
|
||||
"@rollup/rollup-darwin-arm64": "=4.9.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "=4.9.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "=4.9.1",
|
||||
|
@ -83,28 +83,27 @@
|
|||
"@types/mapbox__mapbox-gl-draw": "^1.2.5",
|
||||
"@types/rails__activestorage": "^7.1.1",
|
||||
"@types/rails__ujs": "^6.0.4",
|
||||
"@types/react": "^18.2.79",
|
||||
"@types/react-dom": "^18.2.25",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"@typescript-eslint/eslint-plugin": "^7.11.0",
|
||||
"@typescript-eslint/parser": "^7.11.0",
|
||||
"@vitejs/plugin-react": "^4.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.15.0",
|
||||
"@typescript-eslint/parser": "^7.15.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"axe-core": "^4.8.4",
|
||||
"del-cli": "^5.1.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-react": "^7.34.2",
|
||||
"eslint-plugin-react": "^7.34.3",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"jsdom": "^22.1.0",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^3.3.0",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.12",
|
||||
"jsdom": "^24.1.0",
|
||||
"postcss": "^8.4.39",
|
||||
"prettier": "^3.3.2",
|
||||
"typescript": "^5.5.3",
|
||||
"vite": "^5.3.3",
|
||||
"vite-plugin-full-reload": "^1.1.0",
|
||||
"vite-plugin-ruby": "^5.0.0",
|
||||
"vitest": "^1.6.0"
|
||||
"vitest": "^2.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "del tmp public/graphql && bin/vite clobber",
|
||||
|
|
|
@ -75,8 +75,8 @@ RSpec.describe Attachment::MultipleComponent, type: :component do
|
|||
context 'max attachments' do
|
||||
let(:kwargs) { { max: 1 } }
|
||||
|
||||
it 'does not render visible input file where max attachments has been reached' do
|
||||
expect(subject).to have_selector('.hidden input[type=file]')
|
||||
it 'renders a disabled input file where max attachments has been reached' do
|
||||
expect(subject).to have_selector('input[type=file][disabled]')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -40,12 +40,6 @@ describe Logic::ChampValue do
|
|||
|
||||
it { is_expected.to be nil }
|
||||
end
|
||||
|
||||
context 'with invalid value' do
|
||||
before { champ.value = 'environ 300' }
|
||||
|
||||
it { is_expected.to be nil }
|
||||
end
|
||||
end
|
||||
|
||||
context 'decimal tdc' do
|
||||
|
@ -53,18 +47,6 @@ describe Logic::ChampValue do
|
|||
|
||||
it { expect(champ_value(champ.stable_id).type([champ.type_de_champ])).to eq(:number) }
|
||||
it { is_expected.to eq(42.01) }
|
||||
|
||||
context 'with invalid value with too many digits after the decimal point' do
|
||||
before { champ.value = '42.1234' }
|
||||
|
||||
it { is_expected.to be nil }
|
||||
end
|
||||
|
||||
context 'with invalid value' do
|
||||
before { champ.value = 'racine de 2' }
|
||||
|
||||
it { is_expected.to be nil }
|
||||
end
|
||||
end
|
||||
|
||||
context 'dropdown tdc' do
|
||||
|
|
|
@ -16,17 +16,11 @@ describe 'administrateurs/procedures/show', type: :view do
|
|||
render
|
||||
end
|
||||
|
||||
describe 'publish button is visible' do
|
||||
it { expect(rendered).to have_css('#publish-procedure-link') }
|
||||
it { expect(rendered).not_to have_css('#close-procedure-link') }
|
||||
end
|
||||
|
||||
describe 'procedure path is not customized' do
|
||||
it { expect(rendered).to have_content('En test') }
|
||||
end
|
||||
|
||||
describe 'archive button' do
|
||||
it { expect(rendered).not_to have_css('#archive-procedure') }
|
||||
it "render content" do
|
||||
expect(rendered).to have_css('#publish-procedure-link')
|
||||
expect(rendered).not_to have_css('#close-procedure-link')
|
||||
expect(rendered).to have_content('En test')
|
||||
expect(rendered).not_to have_css('#archive-procedure')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,10 +7,15 @@ RSpec.describe 'commencer/show', type: :view do
|
|||
let(:drafts) { [] }
|
||||
let(:not_drafts) { [] }
|
||||
let(:preview_dossiers) { dossiers.take(3) }
|
||||
let(:user) { nil }
|
||||
|
||||
before do
|
||||
allow(view).to receive(:current_administrateur).and_return(user&.administrateur)
|
||||
end
|
||||
|
||||
before do
|
||||
assign(:procedure, procedure)
|
||||
assign(:revision, procedure.published_revision)
|
||||
assign(:revision, procedure.active_revision)
|
||||
assign(:dossiers, dossiers)
|
||||
assign(:drafts, drafts)
|
||||
assign(:not_drafts, not_drafts)
|
||||
|
@ -25,8 +30,6 @@ RSpec.describe 'commencer/show', type: :view do
|
|||
subject { render }
|
||||
|
||||
context 'when no user is signed in' do
|
||||
let(:user) { nil }
|
||||
|
||||
it 'renders sign-in and sign-up links' do
|
||||
subject
|
||||
expect(rendered).to have_link('Créer un compte')
|
||||
|
@ -98,4 +101,35 @@ RSpec.describe 'commencer/show', type: :view do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "procedure is draft" do
|
||||
let(:procedure) { create(:procedure, :draft) }
|
||||
let(:user) { create :user }
|
||||
|
||||
it 'renders a warning' do
|
||||
subject
|
||||
expect(rendered).to have_text("Cette démarche est actuellement en test")
|
||||
end
|
||||
|
||||
context "when user is admin" do
|
||||
let(:user) { procedure.administrateurs.first.user }
|
||||
|
||||
it "renders warning about draft" do
|
||||
subject
|
||||
expect(rendered).to have_text("Cette démarche est actuellement en test")
|
||||
expect(rendered).to have_text("Ne communiquez pas ce lien")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "revision is draft" do
|
||||
before {
|
||||
assign(:revision, procedure.draft_revision)
|
||||
}
|
||||
|
||||
it "renders warning about draft" do
|
||||
subject
|
||||
expect(rendered).to have_text("Démarche en test")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@ import { defineConfig } from 'vite';
|
|||
import ViteReact from '@vitejs/plugin-react';
|
||||
import RubyPlugin from 'vite-plugin-ruby';
|
||||
import FullReload from 'vite-plugin-full-reload';
|
||||
import optimizeLocales from '@react-aria/optimize-locales-plugin';
|
||||
//import optimizeLocales from '@react-aria/optimize-locales-plugin';
|
||||
|
||||
const plugins = [
|
||||
RubyPlugin(),
|
||||
|
@ -10,13 +10,13 @@ const plugins = [
|
|||
FullReload(
|
||||
['config/routes.rb', 'app/views/**/*', 'app/components/**/*.haml'],
|
||||
{ delay: 200 }
|
||||
),
|
||||
{
|
||||
...optimizeLocales.vite({
|
||||
locales: ['en-GB', 'fr-FR']
|
||||
}),
|
||||
enforce: 'pre' as const
|
||||
}
|
||||
)
|
||||
// {
|
||||
// ...optimizeLocales.vite({
|
||||
// locales: ['en-GB', 'fr-FR']
|
||||
// }),
|
||||
// enforce: 'pre' as const
|
||||
// }
|
||||
];
|
||||
|
||||
export default defineConfig({
|
||||
|
|
Loading…
Reference in a new issue