[Types de Champ Editeur] Save on change and only edited model
This commit is contained in:
parent
dea78e2e4e
commit
5da5f75c5f
14 changed files with 351 additions and 254 deletions
|
@ -1,49 +1,13 @@
|
||||||
module NewAdministrateur
|
module NewAdministrateur
|
||||||
class ProceduresController < AdministrateurController
|
class ProceduresController < AdministrateurController
|
||||||
before_action :retrieve_procedure, only: [:champs, :annotations, :update]
|
before_action :retrieve_procedure, only: [:champs, :annotations]
|
||||||
before_action :procedure_locked?, only: [:champs, :annotations, :update]
|
before_action :procedure_locked?, only: [:champs, :annotations]
|
||||||
|
|
||||||
TYPE_DE_CHAMP_ATTRIBUTES_BASE = [
|
|
||||||
:_destroy,
|
|
||||||
:libelle,
|
|
||||||
:description,
|
|
||||||
:order_place,
|
|
||||||
:type_champ,
|
|
||||||
:id,
|
|
||||||
:mandatory,
|
|
||||||
:piece_justificative_template,
|
|
||||||
:quartiers_prioritaires,
|
|
||||||
:cadastres,
|
|
||||||
:parcelles_agricoles,
|
|
||||||
drop_down_list_attributes: [:value]
|
|
||||||
]
|
|
||||||
|
|
||||||
TYPE_DE_CHAMP_ATTRIBUTES = TYPE_DE_CHAMP_ATTRIBUTES_BASE.dup
|
|
||||||
TYPE_DE_CHAMP_ATTRIBUTES << {
|
|
||||||
types_de_champ_attributes: TYPE_DE_CHAMP_ATTRIBUTES_BASE
|
|
||||||
}
|
|
||||||
|
|
||||||
def apercu
|
def apercu
|
||||||
@dossier = procedure_without_control.new_dossier
|
@dossier = procedure_without_control.new_dossier
|
||||||
@tab = apercu_tab
|
@tab = apercu_tab
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
|
||||||
if @procedure.update(procedure_params)
|
|
||||||
flash.now.notice = if params[:procedure][:types_de_champ_attributes].present?
|
|
||||||
'Formulaire mis à jour.'
|
|
||||||
elsif params[:procedure][:types_de_champ_private_attributes].present?
|
|
||||||
'Annotations privées mises à jour.'
|
|
||||||
else
|
|
||||||
'Démarche enregistrée.'
|
|
||||||
end
|
|
||||||
|
|
||||||
reset_procedure
|
|
||||||
else
|
|
||||||
flash.now.alert = @procedure.errors.full_messages
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def apercu_tab
|
def apercu_tab
|
||||||
|
@ -53,12 +17,5 @@ module NewAdministrateur
|
||||||
def procedure_without_control
|
def procedure_without_control
|
||||||
Procedure.find(params[:id])
|
Procedure.find(params[:id])
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
module NewAdministrateur
|
||||||
|
class TypesDeChampController < AdministrateurController
|
||||||
|
before_action :retrieve_procedure, only: [:create, :update, :destroy]
|
||||||
|
before_action :procedure_locked?, only: [:create, :update, :destroy]
|
||||||
|
|
||||||
|
def create
|
||||||
|
type_de_champ = TypeDeChamp.new(type_de_champ_create_params)
|
||||||
|
|
||||||
|
if type_de_champ.save
|
||||||
|
reset_procedure
|
||||||
|
render json: serialize_type_de_champ(type_de_champ), status: :created
|
||||||
|
else
|
||||||
|
render json: { errors: type_de_champ.errors.full_messages }, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
type_de_champ = TypeDeChamp.where(procedure: @procedure).find(params[:id])
|
||||||
|
|
||||||
|
if type_de_champ.update(type_de_champ_update_params)
|
||||||
|
reset_procedure
|
||||||
|
render json: serialize_type_de_champ(type_de_champ)
|
||||||
|
else
|
||||||
|
render json: { errors: type_de_champ.errors.full_messages }, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
type_de_champ = TypeDeChamp.where(procedure: @procedure).find(params[:id])
|
||||||
|
|
||||||
|
type_de_champ.destroy!
|
||||||
|
reset_procedure
|
||||||
|
|
||||||
|
head :no_content
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def serialize_type_de_champ(type_de_champ)
|
||||||
|
{
|
||||||
|
type_de_champ: type_de_champ.as_json(
|
||||||
|
except: [:created_at, :updated_at, :stable_id, :type, :parent_id, :procedure_id, :private],
|
||||||
|
methods: [:piece_justificative_template_filename, :piece_justificative_template_url, :drop_down_list_value]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def type_de_champ_create_params
|
||||||
|
params.required(:type_de_champ).permit(:libelle,
|
||||||
|
:description,
|
||||||
|
:order_place,
|
||||||
|
:type_champ,
|
||||||
|
:private,
|
||||||
|
:parent_id,
|
||||||
|
:mandatory,
|
||||||
|
:piece_justificative_template,
|
||||||
|
:quartiers_prioritaires,
|
||||||
|
:cadastres,
|
||||||
|
:parcelles_agricoles,
|
||||||
|
:drop_down_list_value).merge(procedure: @procedure)
|
||||||
|
end
|
||||||
|
|
||||||
|
def type_de_champ_update_params
|
||||||
|
params.required(:type_de_champ).permit(:libelle,
|
||||||
|
:description,
|
||||||
|
:order_place,
|
||||||
|
:type_champ,
|
||||||
|
:mandatory,
|
||||||
|
:piece_justificative_template,
|
||||||
|
:quartiers_prioritaires,
|
||||||
|
:cadastres,
|
||||||
|
:parcelles_agricoles,
|
||||||
|
:drop_down_list_value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -39,7 +39,8 @@ module ProcedureHelper
|
||||||
type: "champ",
|
type: "champ",
|
||||||
types_de_champ_options: types_de_champ_options.to_json,
|
types_de_champ_options: types_de_champ_options.to_json,
|
||||||
types_de_champ: types_de_champ_as_json(procedure.types_de_champ).to_json,
|
types_de_champ: types_de_champ_as_json(procedure.types_de_champ).to_json,
|
||||||
direct_uploads_url: rails_direct_uploads_url,
|
save_url: procedure_types_de_champ_path(procedure),
|
||||||
|
direct_upload_url: rails_direct_uploads_url,
|
||||||
drag_icon_url: image_url("icons/drag.svg")
|
drag_icon_url: image_url("icons/drag.svg")
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -49,18 +50,12 @@ module ProcedureHelper
|
||||||
type: "annotation",
|
type: "annotation",
|
||||||
types_de_champ_options: types_de_champ_options.to_json,
|
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,
|
types_de_champ: types_de_champ_as_json(procedure.types_de_champ_private).to_json,
|
||||||
direct_uploads_url: rails_direct_uploads_url,
|
save_url: procedure_types_de_champ_path(procedure),
|
||||||
|
direct_upload_url: rails_direct_uploads_url,
|
||||||
drag_icon_url: image_url("icons/drag.svg")
|
drag_icon_url: image_url("icons/drag.svg")
|
||||||
}
|
}
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
TOGGLES = {
|
TOGGLES = {
|
||||||
|
@ -79,14 +74,12 @@ module ProcedureHelper
|
||||||
types_de_champ
|
types_de_champ
|
||||||
end
|
end
|
||||||
|
|
||||||
TYPES_DE_CHAMP_INCLUDE = { drop_down_list: { only: :value } }
|
|
||||||
TYPES_DE_CHAMP_BASE = {
|
TYPES_DE_CHAMP_BASE = {
|
||||||
except: [:created_at, :updated_at, :stable_id, :type, :parent_id, :procedure_id, :private],
|
except: [:created_at, :updated_at, :stable_id, :type, :parent_id, :procedure_id, :private],
|
||||||
methods: [:piece_justificative_template_filename, :piece_justificative_template_url],
|
methods: [:piece_justificative_template_filename, :piece_justificative_template_url, :drop_down_list_value]
|
||||||
include: TYPES_DE_CHAMP_INCLUDE
|
|
||||||
}
|
}
|
||||||
TYPES_DE_CHAMP = TYPES_DE_CHAMP_BASE
|
TYPES_DE_CHAMP = TYPES_DE_CHAMP_BASE
|
||||||
.merge(include: TYPES_DE_CHAMP_INCLUDE.merge(types_de_champ: TYPES_DE_CHAMP_BASE))
|
.merge(include: { types_de_champ: TYPES_DE_CHAMP_BASE })
|
||||||
|
|
||||||
def types_de_champ_as_json(types_de_champ)
|
def types_de_champ_as_json(types_de_champ)
|
||||||
types_de_champ.includes(:drop_down_list,
|
types_de_champ.includes(:drop_down_list,
|
||||||
|
|
|
@ -1,40 +1,23 @@
|
||||||
|
import { getJSON, debounce } from '@utils';
|
||||||
|
import { DirectUpload } from 'activestorage';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['state', 'update', 'index', 'item'],
|
props: ['state', 'index', 'item'],
|
||||||
computed: {
|
computed: {
|
||||||
isDirty() {
|
isValid() {
|
||||||
return (
|
|
||||||
this.state.version &&
|
|
||||||
this.state.unsavedInvalidItems.size > 0 &&
|
|
||||||
this.state.unsavedItems.has(this.itemId)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
isInvalid() {
|
|
||||||
if (this.deleted) {
|
if (this.deleted) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.libelle) {
|
if (this.libelle) {
|
||||||
return !this.libelle.trim();
|
return !!this.libelle.trim();
|
||||||
}
|
}
|
||||||
return true;
|
return false;
|
||||||
},
|
|
||||||
itemId() {
|
|
||||||
return this.item.id || this.clientId;
|
|
||||||
},
|
|
||||||
changeLog() {
|
|
||||||
return [this.itemId, !this.isInvalid];
|
|
||||||
},
|
},
|
||||||
itemClassName() {
|
itemClassName() {
|
||||||
const classNames = [`draggable-item-${this.index}`];
|
const classNames = [`draggable-item-${this.index}`];
|
||||||
if (this.isHeaderSection) {
|
if (this.isHeaderSection) {
|
||||||
classNames.push('type-header-section');
|
classNames.push('type-header-section');
|
||||||
}
|
}
|
||||||
if (this.isDirty) {
|
|
||||||
if (this.isInvalid) {
|
|
||||||
classNames.push('invalid');
|
|
||||||
} else {
|
|
||||||
classNames.push('dirty');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return classNames.join(' ');
|
return classNames.join(' ');
|
||||||
},
|
},
|
||||||
isDropDown() {
|
isDropDown() {
|
||||||
|
@ -73,6 +56,47 @@ export default {
|
||||||
return 'types_de_champ_attributes';
|
return 'types_de_champ_attributes';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
payload() {
|
||||||
|
const payload = {
|
||||||
|
libelle: this.libelle,
|
||||||
|
type_champ: this.typeChamp,
|
||||||
|
mandatory: this.mandatory,
|
||||||
|
description: this.description,
|
||||||
|
drop_down_list_value: this.dropDownListValue,
|
||||||
|
order_place: this.index
|
||||||
|
};
|
||||||
|
if (this.pieceJustificativeTemplate) {
|
||||||
|
payload.piece_justificative_template = this.pieceJustificativeTemplate;
|
||||||
|
}
|
||||||
|
if (this.state.parentId) {
|
||||||
|
payload.parent_id = this.state.parentId;
|
||||||
|
}
|
||||||
|
if (!this.id && this.state.isAnnotation) {
|
||||||
|
payload.private = true;
|
||||||
|
}
|
||||||
|
Object.assign(payload, this.options);
|
||||||
|
return payload;
|
||||||
|
},
|
||||||
|
saveUrl() {
|
||||||
|
if (this.id) {
|
||||||
|
return `${this.state.saveUrl}/${this.id}`;
|
||||||
|
}
|
||||||
|
return this.state.saveUrl;
|
||||||
|
},
|
||||||
|
savePayload() {
|
||||||
|
if (this.deleted) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return { type_de_champ: this.payload };
|
||||||
|
},
|
||||||
|
saveMethod() {
|
||||||
|
if (this.deleted) {
|
||||||
|
return 'delete';
|
||||||
|
} else if (this.id) {
|
||||||
|
return 'patch';
|
||||||
|
}
|
||||||
|
return 'post';
|
||||||
|
},
|
||||||
typesDeChamp() {
|
typesDeChamp() {
|
||||||
return this.item.types_de_champ;
|
return this.item.types_de_champ;
|
||||||
},
|
},
|
||||||
|
@ -85,39 +109,46 @@ export default {
|
||||||
return Object.assign({}, this.state, {
|
return Object.assign({}, this.state, {
|
||||||
typesDeChamp: this.typesDeChamp,
|
typesDeChamp: this.typesDeChamp,
|
||||||
typesDeChampOptions: this.typesDeChampOptions,
|
typesDeChampOptions: this.typesDeChampOptions,
|
||||||
prefix: `${this.state.prefix}[${this.attribute}][${this.index}]`
|
prefix: `${this.state.prefix}[${this.attribute}][${this.index}]`,
|
||||||
|
parentId: this.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
id: this.item.id,
|
||||||
typeChamp: this.item.type_champ,
|
typeChamp: this.item.type_champ,
|
||||||
libelle: this.item.libelle,
|
libelle: this.item.libelle,
|
||||||
mandatory: this.item.mandatory,
|
mandatory: this.item.mandatory,
|
||||||
description: this.item.description,
|
description: this.item.description,
|
||||||
dropDownList: this.item.drop_down_list && this.item.drop_down_list.value,
|
pieceJustificativeTemplate: null,
|
||||||
|
pieceJustificativeTemplateUrl: this.item.piece_justificative_template_url,
|
||||||
|
pieceJustificativeTemplateFilename: this.item
|
||||||
|
.piece_justificative_template_filename,
|
||||||
|
dropDownListValue: this.item.drop_down_list_value,
|
||||||
deleted: false,
|
deleted: false,
|
||||||
clientId: `id-${clientIds++}`
|
isSaving: false,
|
||||||
|
isUploading: false,
|
||||||
|
hasChanges: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
watch: {
|
||||||
for (let path of PATHS_TO_WATCH) {
|
index() {
|
||||||
this.$watch(path, () => this.update(this.changeLog));
|
this.update();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
created() {
|
||||||
if (this.isInvalid) {
|
this.debouncedSave = debounce(() => this.save(), 500);
|
||||||
this.update(this.changeLog, false);
|
this.debouncedUpload = debounce(evt => this.upload(evt), 500);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
removeChamp(item) {
|
removeChamp() {
|
||||||
if (item.id) {
|
if (this.id) {
|
||||||
this.deleted = true;
|
this.deleted = true;
|
||||||
|
this.debouncedSave();
|
||||||
} else {
|
} else {
|
||||||
const index = this.state.typesDeChamp.indexOf(item);
|
const index = this.state.typesDeChamp.indexOf(this.item);
|
||||||
this.state.typesDeChamp.splice(index, 1);
|
this.state.typesDeChamp.splice(index, 1);
|
||||||
this.update([this.itemId, true]);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
nameFor(name) {
|
nameFor(name) {
|
||||||
|
@ -132,6 +163,76 @@ export default {
|
||||||
type_champ: 'text',
|
type_champ: 'text',
|
||||||
types_de_champ: []
|
types_de_champ: []
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
update() {
|
||||||
|
this.hasChanges = true;
|
||||||
|
if (this.isValid) {
|
||||||
|
if (this.state.inFlight === 0) {
|
||||||
|
this.state.flash.clear();
|
||||||
|
}
|
||||||
|
this.debouncedSave();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
upload(evt) {
|
||||||
|
if (this.isUploading) {
|
||||||
|
this.debouncedUpload();
|
||||||
|
} else {
|
||||||
|
const input = evt.target;
|
||||||
|
const file = input.files[0];
|
||||||
|
if (file) {
|
||||||
|
this.isUploading = true;
|
||||||
|
uploadFile(this.state.directUploadUrl, file).then(({ signed_id }) => {
|
||||||
|
this.pieceJustificativeTemplate = signed_id;
|
||||||
|
this.isUploading = false;
|
||||||
|
this.debouncedSave();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
input.value = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
save() {
|
||||||
|
if (this.isSaving) {
|
||||||
|
this.debouncedSave();
|
||||||
|
} else {
|
||||||
|
this.isSaving = true;
|
||||||
|
this.state.inFlight++;
|
||||||
|
getJSON(this.saveUrl, this.savePayload, this.saveMethod)
|
||||||
|
.then(data => {
|
||||||
|
this.onSuccess(data);
|
||||||
|
})
|
||||||
|
.catch(xhr => {
|
||||||
|
this.onError(xhr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSuccess(data) {
|
||||||
|
if (data && data.type_de_champ) {
|
||||||
|
this.id = data.type_de_champ.id;
|
||||||
|
this.pieceJustificativeTemplateUrl =
|
||||||
|
data.type_de_champ.piece_justificative_template_url;
|
||||||
|
this.pieceJustificativeTemplateFilename =
|
||||||
|
data.type_de_champ.piece_justificative_template_filename;
|
||||||
|
this.pieceJustificativeTemplate = null;
|
||||||
|
}
|
||||||
|
this.state.inFlight--;
|
||||||
|
this.isSaving = false;
|
||||||
|
this.hasChanges = false;
|
||||||
|
|
||||||
|
if (this.state.inFlight === 0) {
|
||||||
|
this.state.flash.success();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError(xhr) {
|
||||||
|
this.isSaving = false;
|
||||||
|
this.state.inFlight--;
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
errors: [message]
|
||||||
|
} = JSON.parse(xhr.responseText);
|
||||||
|
this.state.flash.error(message);
|
||||||
|
} catch (e) {
|
||||||
|
this.state.flash.error(xhr.responseText);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -143,21 +244,20 @@ const EXCLUDE_FROM_REPETITION = [
|
||||||
'siret'
|
'siret'
|
||||||
];
|
];
|
||||||
|
|
||||||
const PATHS_TO_WATCH = [
|
|
||||||
'typeChamp',
|
|
||||||
'libelle',
|
|
||||||
'mandatory',
|
|
||||||
'description',
|
|
||||||
'dropDownList',
|
|
||||||
'options.quartiers_prioritaires',
|
|
||||||
'options.cadastres',
|
|
||||||
'options.parcelles_agricoles',
|
|
||||||
'index',
|
|
||||||
'deleted'
|
|
||||||
];
|
|
||||||
|
|
||||||
function castBoolean(value) {
|
function castBoolean(value) {
|
||||||
return value && value != 0;
|
return value && value != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let clientIds = 0;
|
function uploadFile(directUploadUrl, file) {
|
||||||
|
const upload = new DirectUpload(file, directUploadUrl);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
upload.create((error, blob) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve(blob);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="deleted" v-if="deleted">
|
<div class="deleted" v-if="deleted">
|
||||||
<input type="hidden" :name="nameFor('id')" :value="item.id">
|
<input type="hidden" :name="nameFor('id')" :value="id">
|
||||||
<input type="hidden" :name="nameFor('_destroy')" value="true">
|
<input type="hidden" :name="nameFor('_destroy')" value="true">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@
|
||||||
:id="elementIdFor('type_champ')"
|
:id="elementIdFor('type_champ')"
|
||||||
:name="nameFor('type_champ')"
|
:name="nameFor('type_champ')"
|
||||||
v-model="typeChamp"
|
v-model="typeChamp"
|
||||||
|
@change="update"
|
||||||
class="small-margin small inline">
|
class="small-margin small inline">
|
||||||
<option v-for="option in state.typesDeChampOptions" :key="option[1]" :value="option[1]">
|
<option v-for="option in state.typesDeChampOptions" :key="option[1]" :value="option[1]">
|
||||||
{{ option[0] }}
|
{{ option[0] }}
|
||||||
|
@ -21,21 +22,7 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-start delete">
|
<div class="flex justify-start delete">
|
||||||
<div v-if="isDirty" class="error-message">
|
<button class="button danger" @click.prevent="removeChamp">
|
||||||
<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
|
Supprimer
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,8 +38,9 @@
|
||||||
:id="elementIdFor('libelle')"
|
:id="elementIdFor('libelle')"
|
||||||
:name="nameFor('libelle')"
|
:name="nameFor('libelle')"
|
||||||
v-model="libelle"
|
v-model="libelle"
|
||||||
|
@change="update"
|
||||||
class="small-margin small"
|
class="small-margin small"
|
||||||
:class="{ error: isDirty && isInvalid }">
|
:class="{ error: hasChanges && !isValid }">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="cell" v-show="!isHeaderSection && !isExplication && !state.isAnnotation">
|
<div class="cell" v-show="!isHeaderSection && !isExplication && !state.isAnnotation">
|
||||||
|
@ -65,6 +53,7 @@
|
||||||
:id="elementIdFor('mandatory')"
|
:id="elementIdFor('mandatory')"
|
||||||
:name="nameFor('mandatory')"
|
:name="nameFor('mandatory')"
|
||||||
v-model="mandatory"
|
v-model="mandatory"
|
||||||
|
@change="update"
|
||||||
class="small-margin small"
|
class="small-margin small"
|
||||||
value="1">
|
value="1">
|
||||||
</div>
|
</div>
|
||||||
|
@ -78,6 +67,7 @@
|
||||||
:id="elementIdFor('description')"
|
:id="elementIdFor('description')"
|
||||||
:name="nameFor('description')"
|
:name="nameFor('description')"
|
||||||
v-model="description"
|
v-model="description"
|
||||||
|
@change="update"
|
||||||
rows=3
|
rows=3
|
||||||
cols=40
|
cols=40
|
||||||
class="small-margin small">
|
class="small-margin small">
|
||||||
|
@ -93,7 +83,8 @@
|
||||||
<textarea
|
<textarea
|
||||||
:id="elementIdFor('drop_down_list')"
|
:id="elementIdFor('drop_down_list')"
|
||||||
:name="nameFor('drop_down_list_attributes[value]')"
|
:name="nameFor('drop_down_list_attributes[value]')"
|
||||||
v-model="dropDownList"
|
v-model="dropDownListValue"
|
||||||
|
@change="update"
|
||||||
rows=3
|
rows=3
|
||||||
cols=40
|
cols=40
|
||||||
placeholder="Ecrire une valeur par ligne et --valeur-- pour un séparateur."
|
placeholder="Ecrire une valeur par ligne et --valeur-- pour un séparateur."
|
||||||
|
@ -104,9 +95,9 @@
|
||||||
<label :for="elementIdFor('piece_justificative_template')">
|
<label :for="elementIdFor('piece_justificative_template')">
|
||||||
Modèle
|
Modèle
|
||||||
</label>
|
</label>
|
||||||
<template v-if="item.piece_justificative_template_url">
|
<template v-if="pieceJustificativeTemplateUrl">
|
||||||
<a :href="item.piece_justificative_template_url" target="_blank">
|
<a :href="pieceJustificativeTemplateUrl" target="_blank">
|
||||||
{{item.piece_justificative_template_filename}}
|
{{pieceJustificativeTemplateFilename}}
|
||||||
</a>
|
</a>
|
||||||
<br> Modifier :
|
<br> Modifier :
|
||||||
</template>
|
</template>
|
||||||
|
@ -114,8 +105,7 @@
|
||||||
type="file"
|
type="file"
|
||||||
:id="elementIdFor('piece_justificative_template')"
|
:id="elementIdFor('piece_justificative_template')"
|
||||||
:name="nameFor('piece_justificative_template')"
|
:name="nameFor('piece_justificative_template')"
|
||||||
:data-direct-upload-url="state.directUploadsUrl"
|
@change="upload"
|
||||||
@change="update(changeLog)"
|
|
||||||
class="small-margin small">
|
class="small-margin small">
|
||||||
</div>
|
</div>
|
||||||
<div class="cell" v-show="isCarte">
|
<div class="cell" v-show="isCarte">
|
||||||
|
@ -130,6 +120,7 @@
|
||||||
:id="elementIdFor('quartiers_prioritaires')"
|
:id="elementIdFor('quartiers_prioritaires')"
|
||||||
:name="nameFor('quartiers_prioritaires')"
|
:name="nameFor('quartiers_prioritaires')"
|
||||||
v-model="options.quartiers_prioritaires"
|
v-model="options.quartiers_prioritaires"
|
||||||
|
@change="update"
|
||||||
class="small-margin small"
|
class="small-margin small"
|
||||||
value="1">
|
value="1">
|
||||||
Quartiers prioritaires
|
Quartiers prioritaires
|
||||||
|
@ -141,6 +132,7 @@
|
||||||
:id="elementIdFor('cadastres')"
|
:id="elementIdFor('cadastres')"
|
||||||
:name="nameFor('cadastres')"
|
:name="nameFor('cadastres')"
|
||||||
v-model="options.cadastres"
|
v-model="options.cadastres"
|
||||||
|
@change="update"
|
||||||
class="small-margin small"
|
class="small-margin small"
|
||||||
value="1">
|
value="1">
|
||||||
Cadastres
|
Cadastres
|
||||||
|
@ -152,6 +144,7 @@
|
||||||
:id="elementIdFor('parcelles_agricoles')"
|
:id="elementIdFor('parcelles_agricoles')"
|
||||||
:name="nameFor('parcelles_agricoles')"
|
:name="nameFor('parcelles_agricoles')"
|
||||||
v-model="options.parcelles_agricoles"
|
v-model="options.parcelles_agricoles"
|
||||||
|
@change="update"
|
||||||
class="small-margin small"
|
class="small-margin small"
|
||||||
value="1">
|
value="1">
|
||||||
Parcelles Agricoles
|
Parcelles Agricoles
|
||||||
|
@ -163,7 +156,6 @@
|
||||||
<DraggableItem
|
<DraggableItem
|
||||||
v-for="(item, index) in typesDeChamp"
|
v-for="(item, index) in typesDeChamp"
|
||||||
:state="stateForRepetition"
|
:state="stateForRepetition"
|
||||||
:update="update"
|
|
||||||
:index="index"
|
:index="index"
|
||||||
:item="item"
|
:item="item"
|
||||||
:key="item.id" />
|
:key="item.id" />
|
||||||
|
@ -181,7 +173,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<input type="hidden" :name="nameFor('order_place')" :value="index">
|
<input type="hidden" :name="nameFor('order_place')" :value="index">
|
||||||
<input type="hidden" :name="nameFor('id')" :value="item.id">
|
<input type="hidden" :name="nameFor('id')" :value="id">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
export default {
|
export default {
|
||||||
props: ['state', 'version', 'update', 'updateAll'],
|
props: ['state', 'version'],
|
||||||
methods: {
|
methods: {
|
||||||
addChamp() {
|
addChamp() {
|
||||||
this.state.typesDeChamp.push({
|
this.state.typesDeChamp.push({
|
||||||
type_champ: 'text',
|
type_champ: 'text',
|
||||||
drop_down_list: {},
|
types_de_champ: []
|
||||||
types_de_champ: [],
|
|
||||||
options: {}
|
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
save() {
|
||||||
|
this.state.flash.success();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,14 +10,13 @@
|
||||||
</template>
|
</template>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="button primary" @click.prevent="updateAll">Enregistrer</button>
|
<button class="button primary" @click.prevent="save">Enregistrer</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Draggable :list="state.typesDeChamp" :options="{handle:'.handle'}">
|
<Draggable :list="state.typesDeChamp" :options="{handle:'.handle'}">
|
||||||
<DraggableItem
|
<DraggableItem
|
||||||
v-for="(item, index) in state.typesDeChamp"
|
v-for="(item, index) in state.typesDeChamp"
|
||||||
:state="state"
|
:state="state"
|
||||||
:update="update"
|
|
||||||
:index="index"
|
:index="index"
|
||||||
:item="item"
|
:item="item"
|
||||||
:key="item.id" />
|
:key="item.id" />
|
||||||
|
@ -33,7 +32,7 @@
|
||||||
</template>
|
</template>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="button primary" @click.prevent="updateAll">Enregistrer</button>
|
<button class="button primary" @click.prevent="save">Enregistrer</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Draggable from 'vuedraggable';
|
import Draggable from 'vuedraggable';
|
||||||
import { fire, debounce } from '@utils';
|
|
||||||
|
|
||||||
import DraggableItem from './DraggableItem';
|
import DraggableItem from './DraggableItem';
|
||||||
import DraggableList from './DraggableList';
|
import DraggableList from './DraggableList';
|
||||||
|
@ -16,109 +15,74 @@ addEventListener('DOMContentLoaded', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function initEditor(el) {
|
function initEditor(el) {
|
||||||
const { directUploadsUrl, dragIconUrl } = el.dataset;
|
const { directUploadUrl, dragIconUrl, saveUrl } = el.dataset;
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
typesDeChamp: JSON.parse(el.dataset.typesDeChamp),
|
typesDeChamp: JSON.parse(el.dataset.typesDeChamp),
|
||||||
typesDeChampOptions: JSON.parse(el.dataset.typesDeChampOptions),
|
typesDeChampOptions: JSON.parse(el.dataset.typesDeChampOptions),
|
||||||
directUploadsUrl,
|
directUploadUrl,
|
||||||
dragIconUrl,
|
dragIconUrl,
|
||||||
|
saveUrl,
|
||||||
isAnnotation: el.dataset.type === 'annotation',
|
isAnnotation: el.dataset.type === 'annotation',
|
||||||
unsavedItems: new Set(),
|
prefix: 'procedure',
|
||||||
unsavedInvalidItems: new Set(),
|
inFlight: 0,
|
||||||
version: 1,
|
flash: new Flash()
|
||||||
prefix: 'procedure'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// We add an initial type de champ here if form is empty
|
||||||
|
if (state.typesDeChamp.length === 0) {
|
||||||
|
state.typesDeChamp.push({
|
||||||
|
type_champ: 'text',
|
||||||
|
types_de_champ: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
el,
|
el,
|
||||||
data: {
|
data: {
|
||||||
state,
|
state
|
||||||
update: null
|
|
||||||
},
|
},
|
||||||
render(h) {
|
render(h) {
|
||||||
return h(DraggableList, {
|
return h(DraggableList, {
|
||||||
props: {
|
props: {
|
||||||
state: this.state,
|
state: this.state
|
||||||
update: this.update,
|
|
||||||
updateAll: this.updateAll
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
const [update, updateAll] = createUpdateFunctions(
|
|
||||||
this,
|
|
||||||
state.isAnnotation
|
|
||||||
);
|
|
||||||
|
|
||||||
this.update = update;
|
|
||||||
this.updateAll = updateAll;
|
|
||||||
|
|
||||||
// We add an initial type de champ here if form is empty
|
|
||||||
if (this.state.typesDeChamp.length === 0) {
|
|
||||||
this.state.typesDeChamp.push({
|
|
||||||
type_champ: 'text',
|
|
||||||
types_de_champ: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createUpdateFunctions(app, isAnnotation) {
|
class Flash {
|
||||||
let isSaving = false;
|
constructor(isAnnotation) {
|
||||||
const form = app.$el.closest('form');
|
this.element = document.querySelector('#flash_messages');
|
||||||
|
this.isAnnotation = isAnnotation;
|
||||||
const update = ([id, isValid], refresh = true) => {
|
}
|
||||||
app.state.unsavedItems.add(id);
|
success() {
|
||||||
if (isValid) {
|
if (this.isAnnotation) {
|
||||||
app.state.unsavedInvalidItems.delete(id);
|
this.add('Annotations privées enregistrées.');
|
||||||
} else {
|
} else {
|
||||||
app.state.unsavedInvalidItems.add(id);
|
this.add('Formulaire enregistré.');
|
||||||
}
|
}
|
||||||
if (refresh) {
|
}
|
||||||
app.state.version += 1;
|
error(message) {
|
||||||
}
|
this.add(message, true);
|
||||||
updateAll();
|
}
|
||||||
};
|
clear() {
|
||||||
|
this.element.innerHTML = '';
|
||||||
|
}
|
||||||
|
add(message, isError) {
|
||||||
|
const html = `<div id="flash_message" class="center">
|
||||||
|
<div class="alert alert-fixed ${
|
||||||
|
isError ? 'alert-danger' : 'alert-success'
|
||||||
|
}">
|
||||||
|
${message}
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
const updateAll = debounce(() => {
|
this.element.innerHTML = html;
|
||||||
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 => {
|
setTimeout(() => {
|
||||||
const { types_de_champ, types_de_champ_private } = event.detail;
|
this.clear();
|
||||||
|
}, 6000);
|
||||||
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');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,6 +179,14 @@ class TypeDeChamp < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def drop_down_list_value
|
||||||
|
drop_down_list&.value
|
||||||
|
end
|
||||||
|
|
||||||
|
def drop_down_list_value=(value)
|
||||||
|
self.drop_down_list_attributes = { value: value }
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def setup_procedure
|
def setup_procedure
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
<%= render_flash timeout: 6000, fixed: true %>
|
|
||||||
|
|
||||||
<%= fire_event(:ProcedureUpdated, procedure_data(@procedure.reload)) %>
|
|
|
@ -367,6 +367,8 @@ Rails.application.routes.draw do
|
||||||
get 'champs'
|
get 'champs'
|
||||||
get 'annotations'
|
get 'annotations'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources :types_de_champ, only: [:create, :update, :destroy]
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :services, except: [:show] do
|
resources :services, except: [:show] do
|
||||||
|
|
|
@ -102,7 +102,8 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
|
||||||
|
|
||||||
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle')
|
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'
|
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libelle de champ'
|
||||||
expect(page).to have_content('Formulaire mis à jour')
|
blur
|
||||||
|
expect(page).to have_content('Formulaire enregistré')
|
||||||
|
|
||||||
within '.footer' do
|
within '.footer' do
|
||||||
click_on 'Ajouter un champ'
|
click_on 'Ajouter un champ'
|
||||||
|
@ -129,7 +130,8 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
|
||||||
page.refresh
|
page.refresh
|
||||||
|
|
||||||
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libelle de champ'
|
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libelle de champ'
|
||||||
expect(page).to have_content('Formulaire mis à jour')
|
blur
|
||||||
|
expect(page).to have_content('Formulaire enregistré')
|
||||||
|
|
||||||
click_on Procedure.last.libelle
|
click_on Procedure.last.libelle
|
||||||
click_on 'onglet-pieces'
|
click_on 'onglet-pieces'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
feature 'As an administrateur I edit procedure', js: true do
|
feature 'As an administrateur I can edit types de champ', js: true do
|
||||||
let(:administrateur) { procedure.administrateur }
|
let(:administrateur) { procedure.administrateur }
|
||||||
let(:procedure) { create(:procedure) }
|
let(:procedure) { create(:procedure) }
|
||||||
|
|
||||||
|
@ -18,14 +18,15 @@ feature 'As an administrateur I edit procedure', js: true do
|
||||||
end
|
end
|
||||||
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle')
|
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'
|
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'
|
||||||
expect(page).to have_content('Formulaire mis à jour')
|
blur
|
||||||
|
expect(page).to have_content('Formulaire enregistré')
|
||||||
|
|
||||||
page.refresh
|
page.refresh
|
||||||
within '.footer' do
|
within '.footer' do
|
||||||
click_on 'Enregistrer'
|
click_on 'Enregistrer'
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(page).to have_content('Formulaire mis à jour')
|
expect(page).to have_content('Formulaire enregistré')
|
||||||
end
|
end
|
||||||
|
|
||||||
it "Add multiple champs" do
|
it "Add multiple champs" do
|
||||||
|
@ -34,33 +35,29 @@ feature 'As an administrateur I edit procedure', js: true do
|
||||||
click_on 'Ajouter un champ'
|
click_on 'Ajouter un champ'
|
||||||
click_on 'Ajouter un champ'
|
click_on 'Ajouter un champ'
|
||||||
end
|
end
|
||||||
expect(page).not_to have_content('Le libellé doit être rempli.')
|
expect(page).not_to have_content('Formulaire enregistré')
|
||||||
expect(page).not_to have_content('Modifications non sauvegardées.')
|
|
||||||
expect(page).not_to have_content('Formulaire mis à jour')
|
|
||||||
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ 0'
|
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ 0'
|
||||||
|
fill_in 'procedure_types_de_champ_attributes_1_libelle', with: 'libellé de champ 1'
|
||||||
|
blur
|
||||||
|
expect(page).to have_content('Formulaire enregistré')
|
||||||
|
|
||||||
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle')
|
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_1_libelle')
|
||||||
expect(page).to have_selector('#procedure_types_de_champ_attributes_2_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_selector('#procedure_types_de_champ_attributes_3_libelle')
|
||||||
|
|
||||||
expect(page).to have_content('Le libellé doit être rempli.')
|
within '.draggable-item-2' do
|
||||||
expect(page).to have_content('Modifications non sauvegardées.')
|
|
||||||
expect(page).not_to have_content('Formulaire mis à jour')
|
|
||||||
fill_in 'procedure_types_de_champ_attributes_2_libelle', with: 'libellé de champ 2'
|
|
||||||
|
|
||||||
within '.draggable-item-3' do
|
|
||||||
click_on 'Supprimer'
|
click_on 'Supprimer'
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(page).to have_content('Le libellé doit être rempli.')
|
expect(page).not_to have_selector('#procedure_types_de_champ_attributes_3_libelle')
|
||||||
expect(page).to have_content('Modifications non sauvegardées.')
|
fill_in 'procedure_types_de_champ_attributes_2_libelle', with: 'libellé de champ 2'
|
||||||
expect(page).not_to have_content('Formulaire mis à jour')
|
blur
|
||||||
fill_in 'procedure_types_de_champ_attributes_1_libelle', with: 'libellé de champ 1'
|
expect(page).to have_content('Formulaire enregistré')
|
||||||
|
|
||||||
|
expect(page).to have_content('Supprimer', count: 3)
|
||||||
|
|
||||||
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('Formulaire mis à jour')
|
|
||||||
page.refresh
|
page.refresh
|
||||||
|
|
||||||
expect(page).to have_content('Supprimer', count: 3)
|
expect(page).to have_content('Supprimer', count: 3)
|
||||||
|
@ -68,11 +65,12 @@ feature 'As an administrateur I edit procedure', js: true do
|
||||||
|
|
||||||
it "Remove champs" do
|
it "Remove champs" do
|
||||||
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'
|
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'
|
||||||
expect(page).to have_content('Formulaire mis à jour')
|
blur
|
||||||
|
expect(page).to have_content('Formulaire enregistré')
|
||||||
page.refresh
|
page.refresh
|
||||||
|
|
||||||
click_on 'Supprimer'
|
click_on 'Supprimer'
|
||||||
expect(page).to have_content('Formulaire mis à jour')
|
expect(page).to have_content('Formulaire enregistré')
|
||||||
expect(page).not_to have_content('Supprimer')
|
expect(page).not_to have_content('Supprimer')
|
||||||
page.refresh
|
page.refresh
|
||||||
|
|
||||||
|
@ -82,19 +80,21 @@ feature 'As an administrateur I edit procedure', js: true do
|
||||||
it "Only add valid champs" do
|
it "Only add valid champs" do
|
||||||
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_description')
|
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'
|
fill_in 'procedure_types_de_champ_attributes_0_description', with: 'déscription du champ'
|
||||||
expect(page).to have_content('Le libellé doit être rempli.')
|
blur
|
||||||
expect(page).not_to have_content('Formulaire mis à jour')
|
expect(page).not_to have_content('Formulaire enregistré')
|
||||||
|
|
||||||
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'
|
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'
|
||||||
expect(page).to have_content('Formulaire mis à jour')
|
blur
|
||||||
|
expect(page).to have_content('Formulaire enregistré')
|
||||||
end
|
end
|
||||||
|
|
||||||
it "Add repetition champ" do
|
it "Add repetition champ" do
|
||||||
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle')
|
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle')
|
||||||
select('Bloc répétable', from: 'procedure_types_de_champ_attributes_0_type_champ')
|
select('Bloc répétable', from: 'procedure_types_de_champ_attributes_0_type_champ')
|
||||||
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'
|
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'
|
||||||
|
blur
|
||||||
|
|
||||||
expect(page).to have_content('Formulaire mis à jour')
|
expect(page).to have_content('Formulaire enregistré')
|
||||||
page.refresh
|
page.refresh
|
||||||
|
|
||||||
within '.flex-grow' do
|
within '.flex-grow' do
|
||||||
|
@ -102,8 +102,9 @@ feature 'As an administrateur I edit procedure', js: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
fill_in 'procedure_types_de_champ_attributes_0_types_de_champ_attributes_0_libelle', with: 'libellé de champ 1'
|
fill_in 'procedure_types_de_champ_attributes_0_types_de_champ_attributes_0_libelle', with: 'libellé de champ 1'
|
||||||
|
blur
|
||||||
|
|
||||||
expect(page).to have_content('Formulaire mis à jour')
|
expect(page).to have_content('Formulaire enregistré')
|
||||||
expect(page).to have_content('Supprimer', count: 2)
|
expect(page).to have_content('Supprimer', count: 2)
|
||||||
|
|
||||||
within '.footer' do
|
within '.footer' do
|
||||||
|
@ -112,6 +113,7 @@ feature 'As an administrateur I edit procedure', js: true do
|
||||||
|
|
||||||
select('Bloc répétable', from: 'procedure_types_de_champ_attributes_1_type_champ')
|
select('Bloc répétable', from: 'procedure_types_de_champ_attributes_1_type_champ')
|
||||||
fill_in 'procedure_types_de_champ_attributes_1_libelle', with: 'libellé de champ 2'
|
fill_in 'procedure_types_de_champ_attributes_1_libelle', with: 'libellé de champ 2'
|
||||||
|
blur
|
||||||
|
|
||||||
expect(page).to have_content('Supprimer', count: 3)
|
expect(page).to have_content('Supprimer', count: 3)
|
||||||
end
|
end
|
|
@ -58,6 +58,10 @@ module FeatureHelpers
|
||||||
# Procedure contact infos in the footer
|
# Procedure contact infos in the footer
|
||||||
expect(page).to have_content(procedure.service.email)
|
expect(page).to have_content(procedure.service.email)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def blur
|
||||||
|
page.find('body').click
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
RSpec.configure do |config|
|
RSpec.configure do |config|
|
||||||
|
|
Loading…
Reference in a new issue