Merge pull request #2993 from tchak/new-champs-editor
New champs editor
This commit is contained in:
commit
a92a07c018
37 changed files with 1122 additions and 34 deletions
|
@ -18,7 +18,7 @@ module.exports = {
|
|||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['config/webpack/*.js'],
|
||||
files: ['config/webpack/**/*.js'],
|
||||
env: {
|
||||
node: true
|
||||
}
|
||||
|
|
1
app/assets/images/icons/drag.svg
Normal file
1
app/assets/images/icons/drag.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M20 9H4v2h16V9zM4 15h16v-2H4v2z"/></svg>
|
After Width: | Height: | Size: 132 B |
|
@ -11,6 +11,7 @@
|
|||
|
||||
a {
|
||||
color: $black;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&::after {
|
||||
|
|
|
@ -107,6 +107,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
&.small-margin {
|
||||
margin-bottom: $default-padding / 2;
|
||||
}
|
||||
}
|
||||
|
||||
input[type=text]:not([data-address='true']),
|
||||
input[type=email],
|
||||
input[type=password],
|
||||
|
|
|
@ -20,3 +20,12 @@
|
|||
border-color: $light-green;
|
||||
color: $dark-green;
|
||||
}
|
||||
|
||||
.alert-fixed {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
margin-left: -100px;
|
||||
width: 200px;
|
||||
top: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
|
137
app/assets/stylesheets/new_design/procedure_champs_editor.scss
Normal file
137
app/assets/stylesheets/new_design/procedure_champs_editor.scss
Normal file
|
@ -0,0 +1,137 @@
|
|||
@import "colors";
|
||||
|
||||
#champs-editor {
|
||||
.spinner {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
margin-top: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.draggable-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
border: 1px solid $border-grey;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
|
||||
.handle {
|
||||
cursor: ns-resize;
|
||||
margin-right: 10px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
text-align: center;
|
||||
flex-grow: 1;
|
||||
font-size: 14px;
|
||||
color: $light-grey;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
|
||||
.content {
|
||||
background-color: $medium-red;
|
||||
border-radius: 8px;
|
||||
padding: 4px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&.type-header-section {
|
||||
background-color: $blue;
|
||||
|
||||
label {
|
||||
color: $light-grey;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.type-header-section) {
|
||||
input.error {
|
||||
border: 1px solid $medium-red;
|
||||
}
|
||||
}
|
||||
|
||||
.column {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex-direction: column;
|
||||
|
||||
&.shift-left {
|
||||
margin-left: 35px;
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
|
||||
&.section {
|
||||
padding: 10px 10px 0 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&.hr {
|
||||
border-bottom: 1px solid $border-grey;
|
||||
|
||||
&.head {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&.shift-left {
|
||||
margin-left: 35px;
|
||||
}
|
||||
|
||||
&.head {
|
||||
select {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
&.delete {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.cell {
|
||||
margin-right: 20px;
|
||||
|
||||
&.small {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
&.libelle {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-bottom: 8px;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.carte-options {
|
||||
label {
|
||||
font-weight: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.inline {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.header,
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
|
@ -79,7 +79,12 @@ class Admin::ProceduresController < AdminController
|
|||
if gestionnaire
|
||||
gestionnaire.assign_to_procedure(@procedure)
|
||||
end
|
||||
redirect_to admin_procedure_types_de_champ_path(procedure_id: @procedure.id)
|
||||
|
||||
if Flipflop.new_champs_editor?
|
||||
redirect_to champs_procedure_path(@procedure)
|
||||
else
|
||||
redirect_to admin_procedure_types_de_champ_path(procedure_id: @procedure.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,28 @@
|
|||
module NewAdministrateur
|
||||
class AdministrateurController < ApplicationController
|
||||
before_action :authenticate_administrateur!
|
||||
|
||||
def retrieve_procedure
|
||||
id = params[:procedure_id] || params[:id]
|
||||
|
||||
@procedure = current_administrateur.procedures.find(id)
|
||||
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
flash.alert = 'Démarche inexistante'
|
||||
redirect_to admin_procedures_path
|
||||
end
|
||||
|
||||
def procedure_locked?
|
||||
if @procedure.locked?
|
||||
flash.alert = 'Démarche verrouillée'
|
||||
redirect_to admin_procedure_path(@procedure)
|
||||
end
|
||||
end
|
||||
|
||||
def reset_procedure
|
||||
if @procedure.brouillon_avec_lien?
|
||||
@procedure.reset!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,10 +1,44 @@
|
|||
module NewAdministrateur
|
||||
class ProceduresController < AdministrateurController
|
||||
before_action :retrieve_procedure, only: [:champs, :annotations, :update]
|
||||
before_action :procedure_locked?, only: [:champs, :annotations, :update]
|
||||
|
||||
TYPE_DE_CHAMP_ATTRIBUTES = [
|
||||
:_destroy,
|
||||
:libelle,
|
||||
:description,
|
||||
:order_place,
|
||||
:type_champ,
|
||||
:id,
|
||||
:mandatory,
|
||||
:piece_justificative_template,
|
||||
:quartiers_prioritaires,
|
||||
:cadastres,
|
||||
:parcelles_agricoles,
|
||||
drop_down_list_attributes: [:value]
|
||||
]
|
||||
|
||||
def apercu
|
||||
@dossier = procedure_without_control.new_dossier
|
||||
@tab = apercu_tab
|
||||
end
|
||||
|
||||
def update
|
||||
if @procedure.update(procedure_params)
|
||||
flash.now.notice = if params[:procedure][:types_de_champ_attributes].present?
|
||||
'Champs enregistrés'
|
||||
elsif params[:procedure][:types_de_champ_private_attributes].present?
|
||||
'Annotations enregistrés'
|
||||
else
|
||||
'Démarche enregistrée.'
|
||||
end
|
||||
|
||||
reset_procedure
|
||||
else
|
||||
flash.now.alert = @procedure.errors.full_messages
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def apercu_tab
|
||||
|
@ -14,5 +48,12 @@ module NewAdministrateur
|
|||
def procedure_without_control
|
||||
Procedure.find(params[:id])
|
||||
end
|
||||
|
||||
def procedure_params
|
||||
params.required(:procedure).permit(
|
||||
types_de_champ_attributes: TYPE_DE_CHAMP_ATTRIBUTES,
|
||||
types_de_champ_private_attributes: TYPE_DE_CHAMP_ATTRIBUTES
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,11 +7,20 @@ module ApplicationHelper
|
|||
end
|
||||
end
|
||||
|
||||
def flash_class(level, sticky = false)
|
||||
case level
|
||||
when "notice" then "alert-success#{sticky ? ' sticky' : ''}"
|
||||
when "alert" then "alert-danger#{sticky ? ' sticky' : ''}"
|
||||
def flash_class(level, sticky: false, fixed: false)
|
||||
class_names = case level
|
||||
when 'notice'
|
||||
['alert-success']
|
||||
when 'alert'
|
||||
['alert-danger']
|
||||
end
|
||||
if sticky
|
||||
class_names << 'sticky'
|
||||
end
|
||||
if fixed
|
||||
class_names << 'alert-fixed'
|
||||
end
|
||||
class_names.join(' ')
|
||||
end
|
||||
|
||||
def render_to_element(selector, partial:, outer: false, locals: {})
|
||||
|
@ -22,9 +31,9 @@ module ApplicationHelper
|
|||
# rubocop:enable Rails/OutputSafety
|
||||
end
|
||||
|
||||
def render_flash(timeout: false, sticky: false)
|
||||
def render_flash(timeout: false, sticky: false, fixed: false)
|
||||
if flash.any?
|
||||
html = render_to_element('#flash_messages', partial: 'layouts/flash_messages', locals: { sticky: sticky }, outer: true)
|
||||
html = render_to_element('#flash_messages', partial: 'layouts/flash_messages', locals: { sticky: sticky, fixed: fixed }, outer: true)
|
||||
flash.clear
|
||||
if timeout
|
||||
html += remove_element('#flash_messages', timeout: timeout, inner: true)
|
||||
|
@ -56,6 +65,12 @@ module ApplicationHelper
|
|||
# rubocop:enable Rails/OutputSafety
|
||||
end
|
||||
|
||||
def fire_event(event_name, data)
|
||||
# rubocop:disable Rails/OutputSafety
|
||||
raw("DS.fire('#{event_name}', #{raw(data)});")
|
||||
# rubocop:enable Rails/OutputSafety
|
||||
end
|
||||
|
||||
def current_email
|
||||
current_user&.email ||
|
||||
current_gestionnaire&.email ||
|
||||
|
|
|
@ -33,4 +33,56 @@ module ProcedureHelper
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
def types_de_champ_data(procedure)
|
||||
{
|
||||
type: "champ",
|
||||
types_de_champ_options: types_de_champ_options.to_json,
|
||||
types_de_champ: types_de_champ_as_json(procedure.types_de_champ).to_json,
|
||||
direct_uploads_url: rails_direct_uploads_url,
|
||||
drag_icon_url: image_url("icons/drag.svg")
|
||||
}
|
||||
end
|
||||
|
||||
def types_de_champ_private_data(procedure)
|
||||
{
|
||||
type: "annotation",
|
||||
types_de_champ_options: types_de_champ_options.to_json,
|
||||
types_de_champ: types_de_champ_as_json(procedure.types_de_champ_private).to_json,
|
||||
direct_uploads_url: rails_direct_uploads_url,
|
||||
drag_icon_url: image_url("icons/drag.svg")
|
||||
}
|
||||
end
|
||||
|
||||
def procedure_data(procedure)
|
||||
{
|
||||
types_de_champ: types_de_champ_as_json(procedure.types_de_champ),
|
||||
types_de_champ_private: types_de_champ_as_json(procedure.types_de_champ_private)
|
||||
}.to_json
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
TOGGLES = {
|
||||
TypeDeChamp.type_champs.fetch(:siret) => :champ_siret?,
|
||||
TypeDeChamp.type_champs.fetch(:integer_number) => :champ_integer_number?,
|
||||
TypeDeChamp.type_champs.fetch(:repetition) => :champ_repetition?
|
||||
}
|
||||
|
||||
def types_de_champ_options
|
||||
types_de_champ = TypeDeChamp.type_de_champs_list_fr
|
||||
|
||||
types_de_champ.select! do |tdc|
|
||||
toggle = TOGGLES[tdc.last]
|
||||
toggle.blank? || Flipflop.send(toggle)
|
||||
end
|
||||
|
||||
types_de_champ
|
||||
end
|
||||
|
||||
def types_de_champ_as_json(types_de_champ)
|
||||
types_de_champ.as_json(except: [:created_at, :updated_at],
|
||||
methods: [:piece_justificative_template_filename, :piece_justificative_template_url],
|
||||
include: { drop_down_list: { only: :value } })
|
||||
end
|
||||
end
|
||||
|
|
131
app/javascript/new_design/administrateur/DraggableItem.js
Normal file
131
app/javascript/new_design/administrateur/DraggableItem.js
Normal file
|
@ -0,0 +1,131 @@
|
|||
export default {
|
||||
props: ['state', 'update', 'index', 'item', 'prefix'],
|
||||
computed: {
|
||||
isDirty() {
|
||||
return (
|
||||
this.state.version &&
|
||||
this.state.unsavedInvalidItems.size > 0 &&
|
||||
this.state.unsavedItems.has(this.itemId)
|
||||
);
|
||||
},
|
||||
isInvalid() {
|
||||
if (this.deleted) {
|
||||
return false;
|
||||
}
|
||||
if (this.libelle) {
|
||||
return !this.libelle.trim();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
itemId() {
|
||||
return this.item.id || this.clientId;
|
||||
},
|
||||
changeLog() {
|
||||
return [this.itemId, !this.isInvalid];
|
||||
},
|
||||
itemClassName() {
|
||||
const classNames = [`draggable-item-${this.index}`];
|
||||
if (this.isHeaderSection) {
|
||||
classNames.push('type-header-section');
|
||||
}
|
||||
if (this.isDirty) {
|
||||
if (this.isInvalid) {
|
||||
classNames.push('invalid');
|
||||
} else {
|
||||
classNames.push('dirty');
|
||||
}
|
||||
}
|
||||
return classNames.join(' ');
|
||||
},
|
||||
isDropDown() {
|
||||
return [
|
||||
'drop_down_list',
|
||||
'multiple_drop_down_list',
|
||||
'linked_drop_down_list'
|
||||
].includes(this.typeChamp);
|
||||
},
|
||||
isFile() {
|
||||
return this.typeChamp === 'piece_justificative';
|
||||
},
|
||||
isCarte() {
|
||||
return this.typeChamp === 'carte';
|
||||
},
|
||||
isExplication() {
|
||||
return this.typeChamp === 'explication';
|
||||
},
|
||||
isHeaderSection() {
|
||||
return this.typeChamp === 'header_section';
|
||||
},
|
||||
options() {
|
||||
const options = this.item.options;
|
||||
for (let key of Object.keys(options)) {
|
||||
options[key] = castBoolean(options[key]);
|
||||
}
|
||||
return options;
|
||||
},
|
||||
attribute() {
|
||||
if (this.state.isAnnotation) {
|
||||
return 'types_de_champ_private_attributes';
|
||||
} else {
|
||||
return 'types_de_champ_attributes';
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
typeChamp: this.item.type_champ,
|
||||
libelle: this.item.libelle,
|
||||
mandatory: this.item.mandatory,
|
||||
description: this.item.description,
|
||||
dropDownList: this.item.drop_down_list.value,
|
||||
deleted: false,
|
||||
clientId: `id-${clientIds++}`
|
||||
};
|
||||
},
|
||||
created() {
|
||||
for (let path of PATHS_TO_WATCH) {
|
||||
this.$watch(path, () => this.update(this.changeLog));
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.isInvalid) {
|
||||
this.update(this.changeLog, false);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
removeChamp(item) {
|
||||
if (item.id) {
|
||||
this.deleted = true;
|
||||
} else {
|
||||
const index = this.state.typesDeChamp.indexOf(item);
|
||||
this.state.typesDeChamp.splice(index, 1);
|
||||
this.update([this.itemId, true]);
|
||||
}
|
||||
},
|
||||
nameFor(name) {
|
||||
return `${this.prefix}[${this.attribute}][${this.index}][${name}]`;
|
||||
},
|
||||
elementIdFor(name) {
|
||||
return `${this.prefix}_${this.attribute}_${this.index}_${name}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const PATHS_TO_WATCH = [
|
||||
'typeChamp',
|
||||
'libelle',
|
||||
'mandatory',
|
||||
'description',
|
||||
'dropDownList',
|
||||
'options.quartiers_prioritaires',
|
||||
'options.cadastres',
|
||||
'options.parcelles_agricoles',
|
||||
'index',
|
||||
'deleted'
|
||||
];
|
||||
|
||||
function castBoolean(value) {
|
||||
return value && value != 0;
|
||||
}
|
||||
|
||||
let clientIds = 0;
|
165
app/javascript/new_design/administrateur/DraggableItem.vue
Normal file
165
app/javascript/new_design/administrateur/DraggableItem.vue
Normal file
|
@ -0,0 +1,165 @@
|
|||
<template>
|
||||
<div class="deleted" v-if="deleted">
|
||||
<input type="hidden" :name="nameFor('id')" :value="item.id">
|
||||
<input type="hidden" :name="nameFor('_destroy')" value="true">
|
||||
</div>
|
||||
|
||||
<div class="draggable-item" v-else :class="itemClassName">
|
||||
<div class="row section head" :class="{ hr: !isHeaderSection }">
|
||||
<div class="handle">
|
||||
<img :src="state.dragIconUrl" alt="">
|
||||
</div>
|
||||
<div class="cell">
|
||||
<select :name="nameFor('type_champ')" v-model="typeChamp" class="small-margin small inline">
|
||||
<option v-for="option in state.typesDeChampOptions" :key="option[1]" :value="option[1]">
|
||||
{{ option[0] }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row delete">
|
||||
<div v-if="isDirty" class="error-message">
|
||||
<span v-if="isInvalid" class="content">
|
||||
Le libellé doit être rempli.
|
||||
</span>
|
||||
<span v-else class="content">
|
||||
<template v-if="state.isAnnotation">
|
||||
Modifications non sauvegardées. Le libellé doit être rempli sur tous les annotations.
|
||||
</template>
|
||||
<template v-else>
|
||||
Modifications non sauvegardées. Le libellé doit être rempli sur tous les champs.
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<button class="button danger" @click.prevent="removeChamp(item)">
|
||||
Supprimer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row section" :class="{ hr: isDropDown || isFile || isCarte }">
|
||||
<div class="column shift-left">
|
||||
<div class="cell libelle">
|
||||
<label :for="elementIdFor('libelle')">
|
||||
Libellé
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
:id="elementIdFor('libelle')"
|
||||
:name="nameFor('libelle')"
|
||||
v-model="libelle"
|
||||
class="small-margin small"
|
||||
:class="{ error: isDirty && isInvalid }">
|
||||
</div>
|
||||
|
||||
<div class="cell" v-show="!isHeaderSection && !isExplication && !state.isAnnotation">
|
||||
<label :for="elementIdFor('mandatory')">
|
||||
Obligatoire
|
||||
</label>
|
||||
<input :name="nameFor('mandatory')" type="hidden" value="0">
|
||||
<input
|
||||
type="checkbox"
|
||||
:id="elementIdFor('mandatory')"
|
||||
:name="nameFor('mandatory')"
|
||||
v-model="mandatory"
|
||||
class="small-margin small"
|
||||
value="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="cell" v-show="!isHeaderSection">
|
||||
<label :for="elementIdFor('description')">
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
:id="elementIdFor('description')"
|
||||
:name="nameFor('description')"
|
||||
v-model="description"
|
||||
rows=3
|
||||
cols=40
|
||||
class="small-margin small">
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row section shift-left" v-show="!isHeaderSection">
|
||||
<div class="cell" v-show="isDropDown">
|
||||
<label :for="elementIdFor('drop_down_list')">
|
||||
Liste déroulante
|
||||
</label>
|
||||
<textarea
|
||||
:id="elementIdFor('drop_down_list')"
|
||||
:name="nameFor('drop_down_list_attributes[value]')"
|
||||
v-model="dropDownList"
|
||||
rows=3
|
||||
cols=40
|
||||
placeholder="Ecrire une valeur par ligne et --valeur-- pour un séparateur."
|
||||
class="small-margin small">
|
||||
</textarea>
|
||||
</div>
|
||||
<div class="cell" v-show="isFile">
|
||||
<label :for="elementIdFor('piece_justificative_template')">
|
||||
Modèle
|
||||
</label>
|
||||
<template v-if="item.piece_justificative_template_url">
|
||||
<a :href="item.piece_justificative_template_url" target="_blank">
|
||||
{{item.piece_justificative_template_filename}}
|
||||
</a>
|
||||
<br> Modifier :
|
||||
</template>
|
||||
<input
|
||||
type="file"
|
||||
:id="elementIdFor('piece_justificative_template')"
|
||||
:name="nameFor('piece_justificative_template')"
|
||||
:data-direct-upload-url="state.directUploadsUrl"
|
||||
@change="update(changeLog)"
|
||||
class="small-margin small">
|
||||
</div>
|
||||
<div class="cell" v-show="isCarte">
|
||||
<label>
|
||||
Utilisation de la cartographie
|
||||
</label>
|
||||
<div class="carte-options">
|
||||
<label :for="elementIdFor('quartiers_prioritaires')">
|
||||
<input :name="nameFor('quartiers_prioritaires')" type="hidden" value="0">
|
||||
<input
|
||||
type="checkbox"
|
||||
:id="elementIdFor('quartiers_prioritaires')"
|
||||
:name="nameFor('quartiers_prioritaires')"
|
||||
v-model="options.quartiers_prioritaires"
|
||||
class="small-margin small"
|
||||
value="1">
|
||||
Quartiers prioritaires
|
||||
</label>
|
||||
<label :for="elementIdFor('cadastres')">
|
||||
<input :name="nameFor('cadastres')" type="hidden" value="0">
|
||||
<input
|
||||
type="checkbox"
|
||||
:id="elementIdFor('cadastres')"
|
||||
:name="nameFor('cadastres')"
|
||||
v-model="options.cadastres"
|
||||
class="small-margin small"
|
||||
value="1">
|
||||
Cadastres
|
||||
</label>
|
||||
<label :for="elementIdFor('parcelles_agricoles')">
|
||||
<input :name="nameFor('parcelles_agricoles')" type="hidden" value="0">
|
||||
<input
|
||||
type="checkbox"
|
||||
:id="elementIdFor('parcelles_agricoles')"
|
||||
:name="nameFor('parcelles_agricoles')"
|
||||
v-model="options.parcelles_agricoles"
|
||||
class="small-margin small"
|
||||
value="1">
|
||||
Parcelles Agricoles
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<input type="hidden" :name="nameFor('order_place')" :value="index">
|
||||
<input type="hidden" :name="nameFor('id')" :value="item.id">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./DraggableItem.js"></script>
|
12
app/javascript/new_design/administrateur/DraggableList.js
Normal file
12
app/javascript/new_design/administrateur/DraggableList.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
export default {
|
||||
props: ['state', 'version', 'update', 'updateAll'],
|
||||
methods: {
|
||||
addChamp() {
|
||||
this.state.typesDeChamp.push({
|
||||
type_champ: 'text',
|
||||
drop_down_list: {},
|
||||
options: {}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
42
app/javascript/new_design/administrateur/DraggableList.vue
Normal file
42
app/javascript/new_design/administrateur/DraggableList.vue
Normal file
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<div class="champs-editor">
|
||||
<div v-if="state.typesDeChamp.length > 3" class="header">
|
||||
<button class="button" @click.prevent="addChamp">
|
||||
<template v-if="state.isAnnotation">
|
||||
Ajouter une annotation
|
||||
</template>
|
||||
<template v-else>
|
||||
Ajouter un champ
|
||||
</template>
|
||||
</button>
|
||||
|
||||
<button class="button primary" @click.prevent="updateAll">Enregistrer</button>
|
||||
</div>
|
||||
|
||||
<Draggable :list="state.typesDeChamp" :options="{handle:'.handle'}">
|
||||
<DraggableItem
|
||||
v-for="(item, index) in state.typesDeChamp"
|
||||
prefix="procedure"
|
||||
:state="state"
|
||||
:update="update"
|
||||
:index="index"
|
||||
:item="item"
|
||||
:key="item.id" />
|
||||
</Draggable>
|
||||
|
||||
<div class="footer">
|
||||
<button class="button" @click.prevent="addChamp">
|
||||
<template v-if="state.isAnnotation">
|
||||
Ajouter une annotation
|
||||
</template>
|
||||
<template v-else>
|
||||
Ajouter un champ
|
||||
</template>
|
||||
</button>
|
||||
|
||||
<button class="button primary" @click.prevent="updateAll">Enregistrer</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./DraggableList.js"></script>
|
109
app/javascript/new_design/administrateur/champs-editor.js
Normal file
109
app/javascript/new_design/administrateur/champs-editor.js
Normal file
|
@ -0,0 +1,109 @@
|
|||
import Vue from 'vue';
|
||||
import Draggable from 'vuedraggable';
|
||||
import { fire, debounce } from '@utils';
|
||||
|
||||
import DraggableItem from './DraggableItem';
|
||||
import DraggableList from './DraggableList';
|
||||
|
||||
Vue.component('Draggable', Draggable);
|
||||
Vue.component('DraggableItem', DraggableItem);
|
||||
|
||||
addEventListener('DOMContentLoaded', () => {
|
||||
const el = document.querySelector('#champs-editor');
|
||||
const { directUploadsUrl, dragIconUrl } = el.dataset;
|
||||
|
||||
const state = {
|
||||
typesDeChamp: JSON.parse(el.dataset.typesDeChamp),
|
||||
typesDeChampOptions: JSON.parse(el.dataset.typesDeChampOptions),
|
||||
directUploadsUrl,
|
||||
dragIconUrl,
|
||||
isAnnotation: el.dataset.type === 'annotation',
|
||||
unsavedItems: new Set(),
|
||||
unsavedInvalidItems: new Set(),
|
||||
version: 1
|
||||
};
|
||||
|
||||
new Vue({
|
||||
el,
|
||||
data: {
|
||||
state,
|
||||
update: null
|
||||
},
|
||||
render(h) {
|
||||
return h(DraggableList, {
|
||||
props: {
|
||||
state: this.state,
|
||||
update: this.update,
|
||||
updateAll: this.updateAll
|
||||
}
|
||||
});
|
||||
},
|
||||
mounted() {
|
||||
const [update, updateAll] = createUpdateFunctions(
|
||||
this,
|
||||
state.isAnnotation
|
||||
);
|
||||
|
||||
this.update = update;
|
||||
this.updateAll = updateAll;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function createUpdateFunctions(app, isAnnotation) {
|
||||
let isSaving = false;
|
||||
const form = app.$el.closest('form');
|
||||
|
||||
const update = ([id, isValid], refresh = true) => {
|
||||
app.state.unsavedItems.add(id);
|
||||
if (isValid) {
|
||||
app.state.unsavedInvalidItems.delete(id);
|
||||
} else {
|
||||
app.state.unsavedInvalidItems.add(id);
|
||||
}
|
||||
if (refresh) {
|
||||
app.state.version += 1;
|
||||
}
|
||||
updateAll();
|
||||
};
|
||||
|
||||
const updateAll = debounce(() => {
|
||||
if (isSaving) {
|
||||
updateAll();
|
||||
} else if (
|
||||
app.state.typesDeChamp.length > 0 &&
|
||||
app.state.unsavedInvalidItems.size === 0
|
||||
) {
|
||||
isSaving = true;
|
||||
app.state.unsavedItems.clear();
|
||||
app.state.version += 1;
|
||||
fire(form, 'submit');
|
||||
}
|
||||
}, 500);
|
||||
|
||||
addEventListener('ProcedureUpdated', event => {
|
||||
const { types_de_champ, types_de_champ_private } = event.detail;
|
||||
|
||||
app.state.typesDeChamp = isAnnotation
|
||||
? types_de_champ_private
|
||||
: types_de_champ;
|
||||
isSaving = false;
|
||||
updateFileInputs();
|
||||
});
|
||||
|
||||
return [update, updateAll];
|
||||
}
|
||||
|
||||
// This is needed du to the way ActiveStorage javascript integration works.
|
||||
// It is built to be used with traditional forms. Another way would be to not use
|
||||
// high level ActiveStorage abstractions (and maybe this is what we should do in the future).
|
||||
function updateFileInputs() {
|
||||
for (let element of document.querySelectorAll('.direct-upload')) {
|
||||
let hiddenInput = element.nextElementSibling;
|
||||
let fileInput = hiddenInput.nextElementSibling;
|
||||
element.remove();
|
||||
hiddenInput.remove();
|
||||
fileInput.value = '';
|
||||
fileInput.removeAttribute('disabled');
|
||||
}
|
||||
}
|
|
@ -22,6 +22,8 @@ import '../new_design/select2';
|
|||
import '../new_design/champs/carte';
|
||||
import '../new_design/champs/linked-drop-down-list';
|
||||
|
||||
import '../new_design/administrateur/champs-editor';
|
||||
|
||||
import { toggleCondidentielExplanation } from '../new_design/avis';
|
||||
import { scrollMessagerie } from '../new_design/messagerie';
|
||||
import { showMotivation, motivationCancel } from '../new_design/state-button';
|
||||
|
@ -30,6 +32,7 @@ import { replaceSemicolonByComma } from '../new_design/avis';
|
|||
|
||||
// This is the global application namespace where we expose helpers used from rails views
|
||||
const DS = {
|
||||
fire: (eventName, data) => Rails.fire(document, eventName, data),
|
||||
toggleCondidentielExplanation,
|
||||
scrollMessagerie,
|
||||
showMotivation,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
class DropDownList < ApplicationRecord
|
||||
belongs_to :type_de_champ
|
||||
|
||||
before_validation :clean_value
|
||||
|
||||
def options
|
||||
result = value.split(/[\r\n]|[\r]|[\n]|[\n\r]/).reject(&:empty?)
|
||||
result.blank? ? [] : [''] + result
|
||||
|
@ -13,4 +15,12 @@ class DropDownList < ApplicationRecord
|
|||
def multiple
|
||||
type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:multiple_drop_down_list)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def clean_value
|
||||
value = read_attribute(:value)
|
||||
value = value ? value.split("\r\n").map(&:strip).join("\r\n") : ''
|
||||
write_attribute(:value, value)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,9 +30,9 @@ class Procedure < ApplicationRecord
|
|||
has_one_attached :notice
|
||||
has_one_attached :deliberation
|
||||
|
||||
accepts_nested_attributes_for :types_de_champ, :reject_if => proc { |attributes| attributes['libelle'].blank? }, :allow_destroy => true
|
||||
accepts_nested_attributes_for :types_de_piece_justificative, :reject_if => proc { |attributes| attributes['libelle'].blank? }, :allow_destroy => true
|
||||
accepts_nested_attributes_for :types_de_champ_private
|
||||
accepts_nested_attributes_for :types_de_champ, reject_if: proc { |attributes| attributes['libelle'].blank? }, allow_destroy: true
|
||||
accepts_nested_attributes_for :types_de_champ_private, reject_if: proc { |attributes| attributes['libelle'].blank? }, allow_destroy: true
|
||||
accepts_nested_attributes_for :types_de_piece_justificative, reject_if: proc { |attributes| attributes['libelle'].blank? }, allow_destroy: true
|
||||
|
||||
mount_uploader :logo, ProcedureLogoUploader
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ class TypeDeChamp < ApplicationRecord
|
|||
|
||||
has_one_attached :piece_justificative_template
|
||||
|
||||
accepts_nested_attributes_for :drop_down_list
|
||||
accepts_nested_attributes_for :drop_down_list, update_only: true
|
||||
accepts_nested_attributes_for :types_de_champ, allow_destroy: true
|
||||
|
||||
validates :libelle, presence: true, allow_blank: false, allow_nil: false
|
||||
|
@ -161,6 +161,18 @@ class TypeDeChamp < ApplicationRecord
|
|||
"TypesDeChamp::#{type_champ.classify}TypeDeChamp"
|
||||
end
|
||||
|
||||
def piece_justificative_template_url
|
||||
if piece_justificative_template.attached?
|
||||
Rails.application.routes.url_helpers.url_for(piece_justificative_template)
|
||||
end
|
||||
end
|
||||
|
||||
def piece_justificative_template_filename
|
||||
if piece_justificative_template.attached?
|
||||
piece_justificative_template.filename
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def setup_procedure
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
- champs_editor_path = Flipflop.new_champs_editor? ? champs_procedure_path(@procedure) : admin_procedure_types_de_champ_path(@procedure)
|
||||
.row.white-back
|
||||
.alert.alert-info
|
||||
.form-group
|
||||
%p
|
||||
Pour vos nouveaux besoins de pièces jointes, nous vous invitons à
|
||||
= link_to(admin_procedure_types_de_champ_path(@procedure)) do
|
||||
= link_to(champs_editor_path) do
|
||||
rajouter des champs
|
||||
\ <em>pièce justificative</em> à votre formulaire.
|
||||
|
||||
|
@ -17,7 +18,7 @@
|
|||
%li Support des pièces de grande taille (jusqu’à 200 Mo par pièce)
|
||||
%li Pas de limite de soumission simultanée de plusieurs pièces, pour une expérience usager plus confortable
|
||||
|
||||
= link_to(admin_procedure_types_de_champ_path(@procedure), class: 'btn btn-success') do
|
||||
= link_to(champs_editor_path, class: 'btn btn-success') do
|
||||
Ajouter un champ PJ
|
||||
|
||||
- if @procedure.has_old_pjs?
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
#flash_message.center
|
||||
- flash.each do |key, value|
|
||||
- sticky = defined?(sticky) ? sticky : false
|
||||
- fixed = defined?(fixed) ? fixed : false
|
||||
- if value.class == Array
|
||||
.alert{ class: flash_class(key, sticky) }
|
||||
.alert{ class: flash_class(key, sticky: sticky, fixed: fixed) }
|
||||
- value.each do |message|
|
||||
= sanitize(message)
|
||||
%br
|
||||
- else
|
||||
.alert{ class: flash_class(key, sticky) }
|
||||
.alert{ class: flash_class(key, sticky: sticky, fixed: fixed) }
|
||||
= sanitize(value)
|
||||
|
|
|
@ -31,7 +31,8 @@
|
|||
%p.missing-steps (à compléter)
|
||||
|
||||
- if !@procedure.locked?
|
||||
%a#onglet-champs{ href: url_for(admin_procedure_types_de_champ_path(@procedure)) }
|
||||
- champs_editor_path = Flipflop.new_champs_editor? ? champs_procedure_path(@procedure) : admin_procedure_types_de_champ_path(@procedure)
|
||||
%a#onglet-champs{ href: champs_editor_path }
|
||||
.procedure-list-element{ class: ('active' if active == 'Champs') }
|
||||
Champs
|
||||
|
||||
|
@ -41,7 +42,8 @@
|
|||
Pièces jointes
|
||||
|
||||
- if !@procedure.locked?
|
||||
%a#onglet-private-champs{ href: url_for(admin_procedure_types_de_champ_private_path(@procedure)) }
|
||||
- annotations_editor_path = Flipflop.new_champs_editor? ? annotations_procedure_path(@procedure) : admin_procedure_types_de_champ_private_path(@procedure)
|
||||
%a#onglet-private-champs{ href: annotations_editor_path }
|
||||
.procedure-list-element{ class: ('active' if active == 'Annotations privées') }
|
||||
Annotations privées
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
.sub-header
|
||||
.container
|
||||
.container.flex.justify-between.align-baseline
|
||||
%ul.breadcrumbs
|
||||
- steps.each do |step|
|
||||
%li= step
|
||||
- if defined?(preview) && preview
|
||||
= link_to "Prévisualiser le formulaire", apercu_procedure_path(@procedure), target: "_blank", class: 'button'
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
= render partial: 'new_administrateur/breadcrumbs',
|
||||
locals: { steps: [link_to('Démarches', admin_procedures_path),
|
||||
link_to(@procedure.libelle, admin_procedure_path(@procedure)),
|
||||
'Configuration des annotations privées'], preview: true }
|
||||
|
||||
.container
|
||||
%h1 Configuration des annotations privées
|
||||
%br
|
||||
|
||||
= form_for @procedure, remote: true, html: { class: 'form' } do |form|
|
||||
#champs-editor{ data: types_de_champ_private_data(@procedure) }
|
||||
.spinner
|
12
app/views/new_administrateur/procedures/champs.html.haml
Normal file
12
app/views/new_administrateur/procedures/champs.html.haml
Normal file
|
@ -0,0 +1,12 @@
|
|||
= render partial: 'new_administrateur/breadcrumbs',
|
||||
locals: { steps: [link_to('Démarches', admin_procedures_path),
|
||||
link_to(@procedure.libelle, admin_procedure_path(@procedure)),
|
||||
'Configuration des champs'], preview: true }
|
||||
|
||||
.container
|
||||
%h1 Configuration des champs
|
||||
%br
|
||||
|
||||
= form_for @procedure, remote: true, html: { class: 'form' } do |form|
|
||||
#champs-editor{ data: types_de_champ_data(@procedure) }
|
||||
.spinner
|
3
app/views/new_administrateur/procedures/update.js.erb
Normal file
3
app/views/new_administrateur/procedures/update.js.erb
Normal file
|
@ -0,0 +1,3 @@
|
|||
<%= render_flash timeout: 6000, fixed: true %>
|
||||
|
||||
<%= fire_event(:ProcedureUpdated, procedure_data(@procedure)) %>
|
|
@ -19,6 +19,7 @@ Flipflop.configure do
|
|||
feature :publish_draft
|
||||
feature :support_form
|
||||
feature :enable_email_login_token
|
||||
feature :new_champs_editor
|
||||
|
||||
group :production do
|
||||
feature :remote_storage,
|
||||
|
|
|
@ -357,9 +357,11 @@ Rails.application.routes.draw do
|
|||
#
|
||||
|
||||
scope module: 'new_administrateur' do
|
||||
resources :procedures, only: [] do
|
||||
resources :procedures, only: [:update] do
|
||||
member do
|
||||
get 'apercu'
|
||||
get 'champs'
|
||||
get 'annotations'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
const path = require('path');
|
||||
const { environment } = require('@rails/webpacker');
|
||||
const { VueLoaderPlugin } = require('vue-loader');
|
||||
const vue = require('./loaders/vue');
|
||||
|
||||
const resolve = {
|
||||
alias: {
|
||||
|
@ -9,4 +11,6 @@ const resolve = {
|
|||
|
||||
environment.config.merge({ resolve });
|
||||
|
||||
environment.plugins.append('VueLoaderPlugin', new VueLoaderPlugin());
|
||||
environment.loaders.append('vue', vue);
|
||||
module.exports = environment;
|
||||
|
|
8
config/webpack/loaders/vue.js
Normal file
8
config/webpack/loaders/vue.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
module.exports = {
|
||||
test: /\.vue(\.erb)?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'vue-loader'
|
||||
}
|
||||
]
|
||||
};
|
|
@ -14,6 +14,7 @@ default: &default
|
|||
cache_manifest: false
|
||||
|
||||
extensions:
|
||||
- .vue
|
||||
- .js
|
||||
- .sass
|
||||
- .scss
|
||||
|
|
|
@ -15,7 +15,11 @@
|
|||
"rails-ujs": "^5.2.1",
|
||||
"ramda": "^0.25.0",
|
||||
"select2": "^4.0.6-rc.1",
|
||||
"turbolinks": "^5.2.0"
|
||||
"turbolinks": "^5.2.0",
|
||||
"vue": "^2.5.21",
|
||||
"vue-loader": "^15.5.1",
|
||||
"vue-template-compiler": "^2.5.21",
|
||||
"vuedraggable": "^2.16.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eclint": "^2.8.0",
|
||||
|
|
|
@ -182,6 +182,7 @@ describe Admin::ProceduresController, type: :controller do
|
|||
|
||||
context 'when procedure is correctly save' do
|
||||
before do
|
||||
Flipflop::FeatureSet.current.test!.switch!(:new_champs_editor, true)
|
||||
post :create, params: { procedure: procedure_params }
|
||||
end
|
||||
|
||||
|
@ -197,8 +198,7 @@ describe Admin::ProceduresController, type: :controller do
|
|||
it { expect(subject.duree_conservation_dossiers_hors_ds).to eq(duree_conservation_dossiers_hors_ds) }
|
||||
end
|
||||
|
||||
it { is_expected.to redirect_to(admin_procedure_types_de_champ_path(procedure_id: Procedure.last.id)) }
|
||||
|
||||
it { is_expected.to redirect_to(champs_procedure_path(Procedure.last)) }
|
||||
it { expect(flash[:notice]).to be_present }
|
||||
end
|
||||
|
||||
|
|
|
@ -5,9 +5,11 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
|
|||
include ProcedureSpecHelper
|
||||
|
||||
let(:administrateur) { create(:administrateur, :with_procedure) }
|
||||
let(:test_strategy) { Flipflop::FeatureSet.current.test! }
|
||||
|
||||
before do
|
||||
Flipflop::FeatureSet.current.test!.switch!(:publish_draft, true)
|
||||
test_strategy.switch!(:publish_draft, true)
|
||||
test_strategy.switch!(:new_champs_editor, true)
|
||||
login_as administrateur, scope: :administrateur
|
||||
visit root_path
|
||||
end
|
||||
|
@ -45,13 +47,13 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
|
|||
fill_in_dummy_procedure_details
|
||||
click_on 'save-procedure'
|
||||
|
||||
expect(page).to have_current_path(admin_procedure_types_de_champ_path(Procedure.last))
|
||||
expect(page).to have_current_path(champs_procedure_path(Procedure.last))
|
||||
end
|
||||
end
|
||||
|
||||
context "when publish_draft disabled" do
|
||||
before do
|
||||
Flipflop::FeatureSet.current.test!.switch!(:publish_draft, false)
|
||||
test_strategy.switch!(:publish_draft, false)
|
||||
end
|
||||
|
||||
scenario 'Finding save button for new procedure, libelle, description and cadre_juridique required' do
|
||||
|
@ -68,7 +70,7 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
|
|||
fill_in_dummy_procedure_details(fill_path: false)
|
||||
click_on 'save-procedure'
|
||||
|
||||
expect(page).to have_current_path(admin_procedure_types_de_champ_path(Procedure.last))
|
||||
expect(page).to have_current_path(champs_procedure_path(Procedure.last))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -95,12 +97,22 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
|
|||
end
|
||||
|
||||
scenario 'Add champ, add file, visualize them in procedure preview' do
|
||||
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libelle de champ'
|
||||
click_on 'add_type_de_champ'
|
||||
expect(page).to have_current_path(admin_procedure_types_de_champ_path(Procedure.last))
|
||||
expect(page).to have_selector('#procedure_types_de_champ_attributes_1_libelle')
|
||||
expect(Procedure.last.types_de_champ.first.libelle).to eq('libelle de champ')
|
||||
page.refresh
|
||||
expect(page).to have_current_path(champs_procedure_path(Procedure.last))
|
||||
|
||||
within '.footer' do
|
||||
click_on 'Ajouter un champ'
|
||||
end
|
||||
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle')
|
||||
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libelle de champ'
|
||||
expect(page).to have_content('Champs enregistrés')
|
||||
|
||||
within '.footer' do
|
||||
click_on 'Ajouter un champ'
|
||||
end
|
||||
expect(page).to have_selector('#procedure_types_de_champ_attributes_1_libelle')
|
||||
|
||||
click_on Procedure.last.libelle
|
||||
click_on 'onglet-pieces'
|
||||
expect(page).to have_current_path(admin_procedure_pieces_justificatives_path(Procedure.last))
|
||||
fill_in 'procedure_types_de_piece_justificative_attributes_0_libelle', with: 'libelle de piece'
|
||||
|
@ -117,8 +129,15 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
|
|||
end
|
||||
|
||||
scenario 'After adding champ and file, make publication' do
|
||||
page.refresh
|
||||
|
||||
within '.footer' do
|
||||
click_on 'Ajouter un champ'
|
||||
end
|
||||
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libelle de champ'
|
||||
click_on 'add_type_de_champ'
|
||||
expect(page).to have_content('Champs enregistrés')
|
||||
|
||||
click_on Procedure.last.libelle
|
||||
click_on 'onglet-pieces'
|
||||
|
||||
expect(page).to have_current_path(admin_procedure_pieces_justificatives_path(Procedure.last))
|
||||
|
|
95
spec/features/new_administrateur/procedures_spec.rb
Normal file
95
spec/features/new_administrateur/procedures_spec.rb
Normal file
|
@ -0,0 +1,95 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'As an administrateur I edit procedure', js: true do
|
||||
let(:administrateur) { procedure.administrateur }
|
||||
let(:procedure) { create(:procedure) }
|
||||
|
||||
before do
|
||||
login_as administrateur, scope: :administrateur
|
||||
visit champs_procedure_path(procedure)
|
||||
end
|
||||
|
||||
it "Add a new champ" do
|
||||
within '.footer' do
|
||||
click_on 'Ajouter un champ'
|
||||
end
|
||||
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle')
|
||||
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'
|
||||
expect(page).to have_content('Champs enregistrés')
|
||||
|
||||
page.refresh
|
||||
within '.footer' do
|
||||
click_on 'Enregistrer'
|
||||
end
|
||||
|
||||
expect(page).to have_content('Champs enregistrés')
|
||||
end
|
||||
|
||||
it "Add multiple champs" do
|
||||
within '.footer' do
|
||||
click_on 'Ajouter un champ'
|
||||
click_on 'Ajouter un champ'
|
||||
click_on 'Ajouter un champ'
|
||||
click_on 'Ajouter un champ'
|
||||
end
|
||||
expect(page).not_to have_content('Le libellé doit être rempli.')
|
||||
expect(page).not_to have_content('Modifications non sauvegardées.')
|
||||
expect(page).not_to have_content('Champs enregistrés')
|
||||
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ 0'
|
||||
|
||||
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle')
|
||||
expect(page).to have_selector('#procedure_types_de_champ_attributes_1_libelle')
|
||||
expect(page).to have_selector('#procedure_types_de_champ_attributes_2_libelle')
|
||||
expect(page).to have_selector('#procedure_types_de_champ_attributes_3_libelle')
|
||||
|
||||
expect(page).to have_content('Le libellé doit être rempli.')
|
||||
expect(page).to have_content('Modifications non sauvegardées.')
|
||||
expect(page).not_to have_content('Champs enregistrés')
|
||||
fill_in 'procedure_types_de_champ_attributes_2_libelle', with: 'libellé de champ 2'
|
||||
|
||||
within '.draggable-item-3' do
|
||||
click_on 'Supprimer'
|
||||
end
|
||||
|
||||
expect(page).to have_content('Le libellé doit être rempli.')
|
||||
expect(page).to have_content('Modifications non sauvegardées.')
|
||||
expect(page).not_to have_content('Champs enregistrés')
|
||||
fill_in 'procedure_types_de_champ_attributes_1_libelle', with: 'libellé de champ 1'
|
||||
|
||||
expect(page).not_to have_content('Le libellé doit être rempli.')
|
||||
expect(page).not_to have_content('Modifications non sauvegardées.')
|
||||
expect(page).to have_content('Champs enregistrés')
|
||||
page.refresh
|
||||
|
||||
expect(page).to have_content('Supprimer', count: 3)
|
||||
end
|
||||
|
||||
it "Remove champs" do
|
||||
within '.footer' do
|
||||
click_on 'Ajouter un champ'
|
||||
end
|
||||
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'
|
||||
expect(page).to have_content('Champs enregistrés')
|
||||
page.refresh
|
||||
|
||||
click_on 'Supprimer'
|
||||
expect(page).to have_content('Champs enregistrés')
|
||||
expect(page).not_to have_content('Supprimer')
|
||||
page.refresh
|
||||
|
||||
expect(page).not_to have_content('Supprimer')
|
||||
end
|
||||
|
||||
it "Only add valid champs" do
|
||||
within '.footer' do
|
||||
click_on 'Ajouter un champ'
|
||||
end
|
||||
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_description')
|
||||
fill_in 'procedure_types_de_champ_attributes_0_description', with: 'déscription du champ'
|
||||
expect(page).to have_content('Le libellé doit être rempli.')
|
||||
expect(page).not_to have_content('Champs enregistrés')
|
||||
|
||||
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'
|
||||
expect(page).to have_content('Champs enregistrés')
|
||||
end
|
||||
end
|
149
yarn.lock
149
yarn.lock
|
@ -767,6 +767,21 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
|
||||
integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==
|
||||
|
||||
"@vue/component-compiler-utils@^2.4.0":
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.5.0.tgz#411846d582d393f701f747517ddd29275ce64ca4"
|
||||
integrity sha512-mSB8jWmE/ZeYZHPDEx9hNiiRh5P2V1Q0tObxEQWtxxfXtkIAvPnj7oucGm5SO8Y/QwIlDJgAGqHfj5MCjoKoOg==
|
||||
dependencies:
|
||||
consolidate "^0.15.1"
|
||||
hash-sum "^1.0.2"
|
||||
lru-cache "^4.1.2"
|
||||
merge-source-map "^1.1.0"
|
||||
postcss "^7.0.7"
|
||||
postcss-selector-parser "^5.0.0"
|
||||
prettier "1.13.7"
|
||||
source-map "^0.7.3"
|
||||
vue-template-es2015-compiler "^1.6.0"
|
||||
|
||||
"@webassemblyjs/ast@1.7.11":
|
||||
version "1.7.11"
|
||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.11.tgz#b988582cafbb2b095e8b556526f30c90d057cace"
|
||||
|
@ -1370,6 +1385,11 @@ block-stream@*:
|
|||
dependencies:
|
||||
inherits "~2.0.0"
|
||||
|
||||
bluebird@^3.1.1:
|
||||
version "3.5.3"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
|
||||
integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==
|
||||
|
||||
bluebird@^3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
|
||||
|
@ -1757,6 +1777,15 @@ chalk@^2.0, chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.0, chalk@^2.4.1:
|
|||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.1"
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chardet@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
||||
|
@ -2089,6 +2118,13 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
|
||||
integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
|
||||
|
||||
consolidate@^0.15.1:
|
||||
version "0.15.1"
|
||||
resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7"
|
||||
integrity sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==
|
||||
dependencies:
|
||||
bluebird "^3.1.1"
|
||||
|
||||
constants-browserify@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
|
||||
|
@ -2467,6 +2503,11 @@ date-now@^0.1.4:
|
|||
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
||||
integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=
|
||||
|
||||
de-indent@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
|
||||
integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
|
||||
|
||||
debounce@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131"
|
||||
|
@ -3827,6 +3868,11 @@ hash-base@^3.0.0:
|
|||
inherits "^2.0.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
hash-sum@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04"
|
||||
integrity sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=
|
||||
|
||||
hash.js@^1.0.0, hash.js@^1.0.3:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.5.tgz#e38ab4b85dfb1e0c40fe9265c0e9b54854c23812"
|
||||
|
@ -3835,6 +3881,11 @@ hash.js@^1.0.0, hash.js@^1.0.3:
|
|||
inherits "^2.0.3"
|
||||
minimalistic-assert "^1.0.1"
|
||||
|
||||
he@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
||||
hex-color-regex@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
||||
|
@ -4932,7 +4983,7 @@ lru-cache@^4.0.1, lru-cache@^4.1.1:
|
|||
pseudomap "^1.0.2"
|
||||
yallist "^2.1.2"
|
||||
|
||||
lru-cache@^4.1.3:
|
||||
lru-cache@^4.1.2, lru-cache@^4.1.3:
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
|
||||
integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
|
||||
|
@ -5051,6 +5102,13 @@ merge-descriptors@1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
||||
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
|
||||
|
||||
merge-source-map@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646"
|
||||
integrity sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==
|
||||
dependencies:
|
||||
source-map "^0.6.1"
|
||||
|
||||
methods@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||
|
@ -6568,6 +6626,15 @@ postcss-selector-parser@^4.0.0:
|
|||
indexes-of "^1.0.1"
|
||||
uniq "^1.0.1"
|
||||
|
||||
postcss-selector-parser@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c"
|
||||
integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==
|
||||
dependencies:
|
||||
cssesc "^2.0.0"
|
||||
indexes-of "^1.0.1"
|
||||
uniq "^1.0.1"
|
||||
|
||||
postcss-selector-parser@^5.0.0-rc.4:
|
||||
version "5.0.0-rc.4"
|
||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0-rc.4.tgz#ca5e77238bf152966378c13e91ad6d611568ea87"
|
||||
|
@ -6633,6 +6700,15 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.5:
|
|||
source-map "^0.6.1"
|
||||
supports-color "^5.5.0"
|
||||
|
||||
postcss@^7.0.7:
|
||||
version "7.0.8"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.8.tgz#2a3c5f2bdd00240cd0d0901fd998347c93d36696"
|
||||
integrity sha512-WudsIzuTKRw9IInRTPBgVXJ7DKR26HT09Rxp0g3w0Fqh3TUtYICcUmvC0xURj04o3vdcDtnjCAUCECg/p341iQ==
|
||||
dependencies:
|
||||
chalk "^2.4.2"
|
||||
source-map "^0.6.1"
|
||||
supports-color "^6.0.0"
|
||||
|
||||
prelude-ls@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
||||
|
@ -6650,6 +6726,11 @@ prettier-linter-helpers@^1.0.0:
|
|||
dependencies:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
prettier@1.13.7:
|
||||
version "1.13.7"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.7.tgz#850f3b8af784a49a6ea2d2eaa7ed1428a34b7281"
|
||||
integrity sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==
|
||||
|
||||
prettier@^1.15.3:
|
||||
version "1.15.3"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.15.3.tgz#1feaac5bdd181237b54dbe65d874e02a1472786a"
|
||||
|
@ -7505,6 +7586,11 @@ sort-keys@^2.0.0:
|
|||
dependencies:
|
||||
is-plain-obj "^1.0.0"
|
||||
|
||||
sortablejs@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.7.0.tgz#80a2b2370abd568e1cec8c271131ef30a904fa28"
|
||||
integrity sha1-gKKyNwq9Vo4c7IwnETHvMKkE+ig=
|
||||
|
||||
source-list-map@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085"
|
||||
|
@ -7551,6 +7637,11 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
|
|||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
source-map@^0.7.3:
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
||||
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
|
||||
|
||||
spark-md5@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.0.tgz#3722227c54e2faf24b1dc6d933cc144e6f71bfef"
|
||||
|
@ -7821,6 +7912,13 @@ supports-color@^5.5.0:
|
|||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
supports-color@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a"
|
||||
integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
svgo@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.1.1.tgz#12384b03335bcecd85cfa5f4e3375fed671cb985"
|
||||
|
@ -8410,6 +8508,55 @@ vm-browserify@0.0.4:
|
|||
dependencies:
|
||||
indexof "0.0.1"
|
||||
|
||||
vue-hot-reload-api@^2.3.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.1.tgz#b2d3d95402a811602380783ea4f566eb875569a2"
|
||||
integrity sha512-AA86yKZ5uOKz87/q1UpngEXhbRkaYg1b7HMMVRobNV1IVKqZe8oLIzo6iMocVwZXnYitlGwf2k4ZRLOZlS8oPQ==
|
||||
|
||||
vue-loader@^15.5.1:
|
||||
version "15.5.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.5.1.tgz#e005d5fc2481a55a19910f0dcf434206d68acc2a"
|
||||
integrity sha512-gsTA9xRzu9jGBzkcrAB8my14RkHMzdr5rY/mCFmxgY2tOVsd2Z1MaYCDXHu5nX6PyHAsVK2/hXmarPln/2MiIw==
|
||||
dependencies:
|
||||
"@vue/component-compiler-utils" "^2.4.0"
|
||||
hash-sum "^1.0.2"
|
||||
loader-utils "^1.1.0"
|
||||
vue-hot-reload-api "^2.3.0"
|
||||
vue-style-loader "^4.1.0"
|
||||
|
||||
vue-style-loader@^4.1.0:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8"
|
||||
integrity sha512-0ip8ge6Gzz/Bk0iHovU9XAUQaFt/G2B61bnWa2tCcqqdgfHs1lF9xXorFbE55Gmy92okFT+8bfmySuUOu13vxQ==
|
||||
dependencies:
|
||||
hash-sum "^1.0.2"
|
||||
loader-utils "^1.0.2"
|
||||
|
||||
vue-template-compiler@^2.5.21:
|
||||
version "2.5.21"
|
||||
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.21.tgz#a57ceb903177e8f643560a8d639a0f8db647054a"
|
||||
integrity sha512-Vmk5Cv7UcmI99B9nXJEkaK262IQNnHp5rJYo+EwYpe2epTAXqcVyExhV6pk8jTkxQK2vRc8v8KmZBAwdmUZvvw==
|
||||
dependencies:
|
||||
de-indent "^1.0.2"
|
||||
he "^1.1.0"
|
||||
|
||||
vue-template-es2015-compiler@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz#dc42697133302ce3017524356a6c61b7b69b4a18"
|
||||
integrity sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg==
|
||||
|
||||
vue@^2.5.21:
|
||||
version "2.5.21"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.21.tgz#3d33dcd03bb813912ce894a8303ab553699c4a85"
|
||||
integrity sha512-Aejvyyfhn0zjVeLvXd70h4hrE4zZDx1wfZqia6ekkobLmUZ+vNFQer53B4fu0EjWBSiqApxPejzkO1Znt3joxQ==
|
||||
|
||||
vuedraggable@^2.16.0:
|
||||
version "2.17.0"
|
||||
resolved "https://registry.yarnpkg.com/vuedraggable/-/vuedraggable-2.17.0.tgz#489c16c9bcb9eb06f441944d3c52e5f2bb7060f9"
|
||||
integrity sha512-TAC5tJTSbHSINQCSB59qHnuzT0Ad+E3IgvSWuA1e9UaebD8DxKaY1tCdvL3XvuLoaM3wc1dhpP/NbjpdxYsrng==
|
||||
dependencies:
|
||||
sortablejs "^1.7.0"
|
||||
|
||||
watchpack@^1.5.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"
|
||||
|
|
Loading…
Add table
Reference in a new issue